diff --git a/MoviaBox/Base/Networking/API/SPWalletAPI.swift b/MoviaBox/Base/Networking/API/SPWalletAPI.swift index 92a49b5..022bbdd 100644 --- a/MoviaBox/Base/Networking/API/SPWalletAPI.swift +++ b/MoviaBox/Base/Networking/API/SPWalletAPI.swift @@ -101,4 +101,19 @@ class SPWalletAPI: NSObject { } } + ///金币解锁视频 + static func requestCoinUnlockVideo(shortPlayId: String, videoId: String, completer: ((_ model: SPVideoUnlockModel?) -> Void)?) { + + var param = SPNetworkParameters(path: "/buy_video") + param.isLoding = true + param.parameters = [ + "short_play_id" : shortPlayId, + "video_id" : videoId, + ] + + SPNetwork.request(parameters: param) { (response: SPNetworkResponse) in + completer?(response.data) + } + } + } diff --git a/MoviaBox/Class/Mine/View/SPMineMemberNoView.swift b/MoviaBox/Class/Mine/View/SPMineMemberNoView.swift index f8370a4..5df4efc 100644 --- a/MoviaBox/Class/Mine/View/SPMineMemberNoView.swift +++ b/MoviaBox/Class/Mine/View/SPMineMemberNoView.swift @@ -27,7 +27,7 @@ class SPMineMemberNoView: UIView { private lazy var activateButton: UIButton = { let button = JXButton(type: .custom) - button.leftAnyRightmargin = 13 + button.leftAndRightMargin = 13 button.setTitle("Activate".localized, for: .normal) button.setTitleColor(.colorFFD791(), for: .normal) button.jx_font = .fontMedium(ofSize: 14) diff --git a/MoviaBox/Class/Mine/View/SPMineMemberYesView.swift b/MoviaBox/Class/Mine/View/SPMineMemberYesView.swift index b94413d..320ed69 100644 --- a/MoviaBox/Class/Mine/View/SPMineMemberYesView.swift +++ b/MoviaBox/Class/Mine/View/SPMineMemberYesView.swift @@ -48,7 +48,7 @@ class SPMineMemberYesView: UIView { button.setTitle("Stream Unlimited".localized, for: .normal) button.setTitleColor(.color321704(), for: .normal) button.jx_font = .fontRegular(ofSize: 12) - button.leftAnyRightmargin = 12 + button.leftAndRightMargin = 12 button.colors = [UIColor.colorF2A3A3().cgColor, UIColor.colorFEE095().cgColor] button.locations = [0, 1] button.startPoint = .init(x: 0, y: 0.5) diff --git a/MoviaBox/Class/Player/Controller/SPPlayerDetailViewController.swift b/MoviaBox/Class/Player/Controller/SPPlayerDetailViewController.swift index c834acb..df91136 100644 --- a/MoviaBox/Class/Player/Controller/SPPlayerDetailViewController.swift +++ b/MoviaBox/Class/Player/Controller/SPPlayerDetailViewController.swift @@ -81,8 +81,7 @@ class SPPlayerDetailViewController: SPPlayerListViewController { if videoInfo.is_lock == true { self.pause() - let view = SPPlayBuyView() - view.present(in: nil) + self.onPlayBuy() return } @@ -130,13 +129,12 @@ extension SPPlayerDetailViewController { self.viewModel.handleEpisode = { [weak self] in self?.onEpisode() } + } } extension SPPlayerDetailViewController { - - private func onEpisode() { let view = SPEpisodeView() view.dataArr = detailModel?.episodeList ?? [] @@ -149,6 +147,48 @@ extension SPPlayerDetailViewController { self.episodeView = view } + ///打开支付页面 + private func onPlayBuy() { + let view = SPPlayBuyView() + view.present(in: nil) + } + + ///解锁视频 + private func unlockVideo(indexPath: IndexPath) { + + guard let videoInfo = detailModel?.episodeList?[indexPath.row] else { return } + + guard let shortPlayId = videoInfo.short_play_id, let videoId = videoInfo.short_play_video_id else { return } + + SPWalletAPI.requestCoinUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in + guard let self = self else { return } + guard let model = model else { return } + + switch model.status { + case .jump: + SPToast.show(text: "kAlertMessage_01".localized) + + case .noPlay: + SPToast.show(text: "kAlertMessage_02".localized) + + case .notEnough: + self.onPlayBuy() + + case .success: + videoInfo.is_lock = false + self.reloadData { [weak self] in + guard let self = self else { return } + self.play() + } + + default: + break + } + + + } + + } } //MARK: -------------- SPPlayerListViewControllerDataSource -------------- @@ -158,6 +198,20 @@ extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SP cell.shortModel = detailModel?.shortPlayInfo cell.videoInfo = detailModel?.episodeList?[indexPath.row] cell.isLoop = false + + let upRow = indexPath.row - 1 + if upRow >= 0, let videoInfo = detailModel?.episodeList?[upRow], videoInfo.is_lock == true { + cell.hasLockUpEpisode = true + } else { + cell.hasLockUpEpisode = false + } + + cell.clickUnlockButton = { [weak self] (cell) in + guard let self = self else { return } + guard let indexPath = self.collectionView.indexPath(for: cell) else { return } + self.unlockVideo(indexPath: indexPath) + } + } return oldCell } diff --git a/MoviaBox/Class/Player/Controller/SPPlayerListViewController.swift b/MoviaBox/Class/Player/Controller/SPPlayerListViewController.swift index 67f1d2b..c14b555 100644 --- a/MoviaBox/Class/Player/Controller/SPPlayerListViewController.swift +++ b/MoviaBox/Class/Player/Controller/SPPlayerListViewController.swift @@ -186,7 +186,11 @@ class SPPlayerListViewController: SPViewController { } func reloadData(completion: (() -> Void)? = nil) { - CATransaction.setCompletionBlock { + CATransaction.setCompletionBlock { [weak self] in + guard let self = self else { return } + let cell = self.collectionView.cellForItem(at: self.currentIndexPath) as? SPPlayerListCell + self.viewModel.currentPlayer = cell + completion?() } CATransaction.begin() diff --git a/MoviaBox/Class/Player/View/SPPlayLockView.swift b/MoviaBox/Class/Player/View/SPPlayLockView.swift new file mode 100644 index 0000000..3c162f1 --- /dev/null +++ b/MoviaBox/Class/Player/View/SPPlayLockView.swift @@ -0,0 +1,175 @@ +// +// SPPlayLockView.swift +// MoviaBox +// +// Created by 佳尔 on 2025/5/6. +// + +import UIKit + +class SPPlayLockView: UIView { + + ///是否是解锁上一集 + var isUnlockUpEpisode: Bool = false { + didSet { + updateUnlockButton() + } + } + + var videoInfo: SPVideoInfoModel? { + didSet { + coinView.setTitle("\(videoInfo?.coins ?? 0)", for: .normal) + } + } + + ///点击解锁按钮 + var clickUnlockButton: (() -> Void)? + + //MARK: UI属性 + private lazy var containerView: UIView = { + let view = UIView() + return view + }() + + private lazy var lockImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "lock_icon_02")) + return imageView + }() + + private lazy var lockTextLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 18) + label.textColor = .colorFFFFFF() + label.text = "This episode is locked".localized + return label + }() + + private lazy var unlockButton: UIButton = { + let button = UIButton() + button.backgroundColor = .colorFF3232() + button.layer.cornerRadius = 27 + button.layer.masksToBounds = true + button.addTarget(self, action: #selector(handleUnlockButton), for: .touchUpInside) + return button + }() + + private lazy var unlockStackView: UIStackView = { + let view = UIStackView() + view.isUserInteractionEnabled = false + view.axis = .horizontal + view.spacing = 6 + view.alignment = .center + return view + }() + + private lazy var unlockImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "unlock_icon_01")) + return imageView + }() + + private lazy var unlockTitleLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 16) + label.textColor = .colorFFFFFF() + return label + }() + + private lazy var coinView: UIButton = { + let view = JXButton(type: .custom) + view.isUserInteractionEnabled = false + view.backgroundColor = .color000000(alpha: 0.2) + view.layer.cornerRadius = 19 + view.layer.masksToBounds = true + view.jx_font = .fontRegular(ofSize: 16) + view.setTitleColor(.colorFFFFFF(), for: .normal) + view.setImage(UIImage(named: "coin_icon_04"), for: .normal) + view.space = 8 + view.leftAndRightMargin = 8 + view.snp.makeConstraints { make in + make.height.equalTo(38) + } + return view + }() + + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .color000000(alpha: 0.75) + updateUnlockButton() + + _setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + @objc private func handleUnlockButton() { + if isUnlockUpEpisode { + SPToast.show(text: "kAlertMessage_01".localized) + return + } + + self.clickUnlockButton?() + + } + + private func updateUnlockButton() { + unlockStackView.removeAllArrangedSubview() + + unlockStackView.addArrangedSubview(unlockImageView) + unlockStackView.addArrangedSubview(unlockTitleLabel) + + if isUnlockUpEpisode { + unlockTitleLabel.text = "Unlock the previous episode".localized + + } else { + unlockTitleLabel.text = "Unlock now for".localized + + unlockStackView.addArrangedSubview(coinView) + } + } + +} + +extension SPPlayLockView { + + private func _setupUI() { + addSubview(containerView) + containerView.addSubview(lockImageView) + containerView.addSubview(lockTextLabel) + containerView.addSubview(unlockButton) + unlockButton.addSubview(unlockStackView) + + containerView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.right.equalToSuperview() + } + + lockImageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview() + } + + lockTextLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(lockImageView.snp.bottom).offset(13) + } + + unlockButton.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.left.equalToSuperview().offset(22) + make.bottom.equalToSuperview() + make.top.equalTo(lockTextLabel.snp.bottom).offset(35) + make.height.equalTo(54) + } + + unlockStackView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.centerX.equalToSuperview() + } + + } + +} diff --git a/MoviaBox/Class/Player/View/SPPlayerControlView.swift b/MoviaBox/Class/Player/View/SPPlayerControlView.swift index b7cadc3..8a5fb5e 100644 --- a/MoviaBox/Class/Player/View/SPPlayerControlView.swift +++ b/MoviaBox/Class/Player/View/SPPlayerControlView.swift @@ -107,9 +107,6 @@ class SPPlayerControlView: UIView { return button }() - ///锁定视图 -// private lazy var lock - deinit { viewModel?.removeObserver(self, forKeyPath: "isPlaying") NotificationCenter.default.removeObserver(self) diff --git a/MoviaBox/Class/Player/View/SPPlayerDetailCell.swift b/MoviaBox/Class/Player/View/SPPlayerDetailCell.swift index ee16d19..5cbe70e 100644 --- a/MoviaBox/Class/Player/View/SPPlayerDetailCell.swift +++ b/MoviaBox/Class/Player/View/SPPlayerDetailCell.swift @@ -13,9 +13,26 @@ class SPPlayerDetailCell: SPPlayerListCell { return SPPlayerDetailControlView.self } + ///点击解锁按钮 + var clickUnlockButton: ((_ cell: SPPlayerDetailCell) -> Void)? + + ///上一集是否加锁 + var hasLockUpEpisode = false { + didSet { + guard let controlView = self.controlView as? SPPlayerDetailControlView else { return } + controlView.hasLockUpEpisode = hasLockUpEpisode + } + } override init(frame: CGRect) { super.init(frame: frame) + if let controlView = self.controlView as? SPPlayerDetailControlView { + controlView.clickUnlockButton = { [weak self] in + guard let self = self else { return } + self.clickUnlockButton?(self) + } + + } } @MainActor required init?(coder: NSCoder) { diff --git a/MoviaBox/Class/Player/View/SPPlayerDetailControlView.swift b/MoviaBox/Class/Player/View/SPPlayerDetailControlView.swift index 55198fe..9687081 100644 --- a/MoviaBox/Class/Player/View/SPPlayerDetailControlView.swift +++ b/MoviaBox/Class/Player/View/SPPlayerDetailControlView.swift @@ -72,6 +72,27 @@ class SPPlayerDetailControlView: SPPlayerControlView { } } + override var videoInfo: SPVideoInfoModel? { + didSet { + if videoInfo?.is_lock == true { + lockView.isHidden = false + lockView.videoInfo = videoInfo + } else { + lockView.isHidden = true + } + } + } + + ///上一集是否加锁 + var hasLockUpEpisode = false { + didSet { + lockView.isUnlockUpEpisode = hasLockUpEpisode + } + } + + ///点击解锁按钮 + var clickUnlockButton: (() -> Void)? + ///暂停按钮倒计时 private var timer: Timer? @@ -129,6 +150,15 @@ class SPPlayerDetailControlView: SPPlayerControlView { return button }() + ///锁定视图 + private lazy var lockView: SPPlayLockView = { + let view = SPPlayLockView() + view.clickUnlockButton = { [weak self] in + self?.clickUnlockButton?() + } + return view + }() + deinit { self.viewModel?.removeObserver(self, forKeyPath: "speedModel") } @@ -219,6 +249,8 @@ extension SPPlayerDetailControlView { toolView.addSubview(progressTimeLabel) addSubview(retreatButton) addSubview(advanceButton) + addSubview(lockView) +// self.bringSubviewToFront(rightFeatureView) self.progressTimeLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() @@ -235,6 +267,10 @@ extension SPPlayerDetailControlView { make.left.equalTo(playImageView.snp.right).offset(kSPMainW(50)) } + lockView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } } diff --git a/MoviaBox/Class/Player/View/SPPlayerListCell.swift b/MoviaBox/Class/Player/View/SPPlayerListCell.swift index cd49b9b..c7ce1ad 100644 --- a/MoviaBox/Class/Player/View/SPPlayerListCell.swift +++ b/MoviaBox/Class/Player/View/SPPlayerListCell.swift @@ -43,7 +43,7 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol { return imageView }() - private lazy var controlView: SPPlayerControlView = { + private(set) lazy var controlView: SPPlayerControlView = { let view = PlayerControlViewClass.init() view.panProgressFinishBlock = { [weak self] progress in guard let self = self else { return } diff --git a/MoviaBox/Class/Wallet/Model/SPVideoUnlockModel.swift b/MoviaBox/Class/Wallet/Model/SPVideoUnlockModel.swift new file mode 100644 index 0000000..a6c00da --- /dev/null +++ b/MoviaBox/Class/Wallet/Model/SPVideoUnlockModel.swift @@ -0,0 +1,26 @@ +// +// SPVideoUnlockModel.swift +// MoviaBox +// +// Created by 佳尔 on 2025/5/6. +// + +import UIKit +import SmartCodable + +class SPVideoUnlockModel: SPModel, SmartCodable { + + enum ResponseStatus: String, SmartCaseDefaultable { + ///前面还有没购买的剧 + case jump = "jump" + ///没找到视频 + case noPlay = "no_play" + ///金币不足跳充值 + case notEnough = "not_enough" + ///购买成功 + case success = "success" + } + + var status: ResponseStatus? + +} diff --git a/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Contents.json b/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Contents.json new file mode 100644 index 0000000..597a9e4 --- /dev/null +++ b/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 125@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 125@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@2x.png b/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@2x.png new file mode 100644 index 0000000..48f1088 Binary files /dev/null and b/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@2x.png differ diff --git a/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@3x.png b/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@3x.png new file mode 100644 index 0000000..2382137 Binary files /dev/null and b/MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@3x.png differ diff --git a/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Contents.json b/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@2x.png b/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@2x.png new file mode 100644 index 0000000..a3dad5d Binary files /dev/null and b/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@2x.png differ diff --git a/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@3x.png b/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@3x.png new file mode 100644 index 0000000..0d61103 Binary files /dev/null and b/MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@3x.png differ diff --git a/MoviaBox/Source/en.lproj/Localizable.strings b/MoviaBox/Source/en.lproj/Localizable.strings index e7ef05e..d4319e8 100644 --- a/MoviaBox/Source/en.lproj/Localizable.strings +++ b/MoviaBox/Source/en.lproj/Localizable.strings @@ -85,7 +85,14 @@ "Check in" = "Check in"; "Recharge Coins" = "Recharge Coins"; "Purchase VIP" = "Purchase VIP"; +"This episode is locked" = "This episode is locked"; +"Unlock now for" = "Unlock now for"; +"Unlock the previous episode" = "Unlock the previous episode"; +///请购买上一集提示 +"kAlertMessage_01" = "The previous episode of this series has not been unlocked yet. Please unlock the previous episode first."; +///没有找到视频提示 +"kAlertMessage_02" = "Purchase failed, please try again later!"; "kLoginAgreementText" = "By continuing, you agree to the User Agreement and Privacy Policy"; "kBuyMemberTipText" = "Auto renew · Cancel anytime"; diff --git a/MoviaBox/Thirdparty/JXButton/JXButton.swift b/MoviaBox/Thirdparty/JXButton/JXButton.swift index 246bd18..c252be2 100644 --- a/MoviaBox/Thirdparty/JXButton/JXButton.swift +++ b/MoviaBox/Thirdparty/JXButton/JXButton.swift @@ -17,7 +17,7 @@ class JXButton: UIButton { var maxTitleWidth: CGFloat = 0 var titleDirection: UITextLayoutDirection? ///左右边距 - var leftAnyRightmargin: CGFloat = 0 + var leftAndRightMargin: CGFloat = 0 ///文字与图片的间距 var space: CGFloat = 0 @@ -96,7 +96,7 @@ class JXButton: UIButton { } } - let size = CGSize(width: width + leftAnyRightmargin * 2, height: height) + let size = CGSize(width: width + leftAndRightMargin * 2, height: height) return size }