解锁视频开发
This commit is contained in:
parent
e4e92ee9bf
commit
b710eb9ac1
@ -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<SPVideoUnlockModel>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
175
MoviaBox/Class/Player/View/SPPlayLockView.swift
Normal file
175
MoviaBox/Class/Player/View/SPPlayLockView.swift
Normal file
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -107,9 +107,6 @@ class SPPlayerControlView: UIView {
|
||||
return button
|
||||
}()
|
||||
|
||||
///锁定视图
|
||||
// private lazy var lock
|
||||
|
||||
deinit {
|
||||
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 }
|
||||
|
26
MoviaBox/Class/Wallet/Model/SPVideoUnlockModel.swift
Normal file
26
MoviaBox/Class/Wallet/Model/SPVideoUnlockModel.swift
Normal file
@ -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?
|
||||
|
||||
}
|
22
MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Contents.json
vendored
Normal file
22
MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
BIN
MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@2x.png
vendored
Normal file
BIN
MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@3x.png
vendored
Normal file
BIN
MoviaBox/Source/Assets.xcassets/icon/lock_icon_02.imageset/Frame 125@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
22
MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Contents.json
vendored
Normal file
22
MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
BIN
MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@2x.png
vendored
Normal file
BIN
MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 400 B |
BIN
MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@3x.png
vendored
Normal file
BIN
MoviaBox/Source/Assets.xcassets/icon/unlock_icon_01.imageset/Frame@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 534 B |
@ -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";
|
||||
|
4
MoviaBox/Thirdparty/JXButton/JXButton.swift
vendored
4
MoviaBox/Thirdparty/JXButton/JXButton.swift
vendored
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user