选集功能开发

This commit is contained in:
曾觉新 2025-04-16 18:22:12 +08:00
parent a318806a2d
commit 7a67b5824e
36 changed files with 926 additions and 44 deletions

View File

@ -14,7 +14,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.appConfig()
SPLoginManager.manager.requestVisitorLogin(completer: nil)

View File

@ -26,3 +26,11 @@ extension String: SmartCodable {
}
}
extension String {
///Size
func size(font: UIFont, size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))) -> CGSize{
let string: NSString = self as NSString
return string.size(for: font, size: size, mode: .byWordWrapping)
}
}

View File

@ -63,5 +63,7 @@ extension UIColor {
static func colorF56490(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xF56490, alpha: alpha)
}
}

View File

@ -41,4 +41,37 @@ class SPVideoAPI: NSObject {
}
}
///
static func requestCollectShort(isCollect: Bool, shortPlayId: String, success: (() -> Void)?) {
let path: String
if isCollect {
path = "/collect"
} else {
path = "/cancelCollect"
}
var param = SPNetworkParameters(path: path)
param.isLoding = true
param.parameters = [
"short_play_id" : shortPlayId
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<String>) in
if response.code == SPNetworkCodeSucceed {
success?()
NotificationCenter.default.post(name: SPVideoAPI.updateShortCollectStateNotification, object: nil, userInfo: [
"state" : isCollect,
"id" : shortPlayId,
])
}
}
}
}
extension SPVideoAPI {
/// [ "state" : isCollect, "id" : shortPlayId,]
@objc static let updateShortCollectStateNotification = NSNotification.Name(rawValue: "SPVideoAPI.updateShortCollectStateNotification")
}

View File

@ -0,0 +1,101 @@
//// CryptorService.swift
// QJDrama
//
// Created by Yao on 2025/4/16
//
//
import Foundation
class CryptorService {
//
static let EN_STR_TAG: String = "$" //
//
static func decrypt(data: String) -> String {
guard data.hasPrefix(EN_STR_TAG) else {
// fatalError("Invalid encoded string")
return data
}
let decryptedData = deStrBytes(data: data)
return String(data: decryptedData, encoding: .utf8) ?? ""
}
//
static func deStrBytes(data: String) -> Data {
let hexData = String(data.dropFirst())
var bytes = Data()
var index = hexData.startIndex
while index < hexData.endIndex {
let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex
let byteString = String(hexData[index..<nextIndex])
if let byte = UInt8(byteString, radix: 16) {
bytes.append(byte)
}
index = nextIndex
}
return de(data: bytes)
}
//
static func de(data: Data) -> Data {
guard !data.isEmpty else {
return data
}
let saltLen = Int(data[data.startIndex])
guard data.count >= 1 + saltLen else {
return data
}
let salt = data.subdata(in: 1..<1+saltLen)
let encryptedData = data.subdata(in: 1+saltLen..<data.count)
return deWithSalt(data: encryptedData, salt: salt)
}
// 使
static func deWithSalt(data: Data, salt: Data) -> Data {
let decryptedData = cxEd(data: data)
return removeSalt(data: decryptedData, salt: salt)
}
// /
static func cxEd(data: Data) -> Data {
return Data(data.map { $0 ^ 0xFF })
}
//
static func removeSalt(data: Data, salt: Data) -> Data {
guard !salt.isEmpty else {
return data
}
var result = Data()
let saltBytes = [UInt8](salt)
let saltCount = saltBytes.count
for (index, byte) in data.enumerated() {
let saltByte = saltBytes[index % saltCount]
let decryptedByte = calRemoveSalt(v: byte, s: saltByte)
result.append(decryptedByte)
}
return result
}
//
static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 {
if v >= s {
return v - s
} else {
return UInt8(0xFF) - (s - v) + 1
}
}
}

View File

@ -34,11 +34,14 @@ class SPNetwork: NSObject {
while loding {
if !SPLoginManager.manager.isRefreshingToken {
loding = false
spLog(message: "======等待结束")
} else {
spLog(message: "======等待中")
}
RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
}
spLog(message: "======开始请求")
_request(parameters: parameters, completion: completion)
}
@ -140,11 +143,11 @@ class SPNetwork: NSObject {
var response: SPNetworkResponse<T>?
let time = Date().timeIntervalSince1970
if let decrypted = SPCryptService.decrypt(data) {
spLog(message: decrypted)
response = SPNetworkResponse<T>.deserialize(from: decrypted)
response?.rawData = decrypted
}
let decrypted = CryptorService.decrypt(data: data)
spLog(message: decrypted)
response = SPNetworkResponse<T>.deserialize(from: decrypted)
response?.rawData = decrypted
spLog(message: Date().timeIntervalSince1970 - time)
if let response = response {

View File

@ -7,6 +7,18 @@
import UIKit
/*
https://api-moviatv.moviatv.com/93f03506/
https://api-mireotv.mireotv.com/4da6fd4c/
https://api-vibeoshort.vibeoshort.com/bf86d973/
https://api-viontv.viontv.com/b7afef99/
https://api-zyreotv.zyreotv.com/7834f11d/
*/
#if DEBUG
let SPBaseURL = "https://test1-api.guyantv.com"
let SPWebBaseURL = "https://www.guyantv.com"

View File

@ -46,7 +46,7 @@ extension SPForYouViewController: SPPlayerListViewControllerDataSource {
if let cell = oldCell as? SPPlayerListCell {
if let model = dataArr[indexPath.row] as? SPShortModel {
cell.model = model
cell.shortModel = model
cell.videoInfo = model.video_info
}
}

View File

@ -106,7 +106,7 @@ extension SPHomeHeaderView: ZKCycleScrollViewDelegate, ZKCycleScrollViewDataSour
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
let model = moduleModel?.bannerData?[index]
let vc = SPTVPlayerListViewController()
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}

View File

@ -83,7 +83,7 @@ extension SPHomeHotView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.dataArr?[indexPath.row]
let vc = SPTVPlayerListViewController()
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}

View File

@ -101,7 +101,7 @@ extension SPHomeTrendingView: UICollectionViewDataSource, UICollectionViewDelega
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.dataArr?[indexPath.row]
let vc = SPTVPlayerListViewController()
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}

View File

@ -1,5 +1,5 @@
//
// SPTVPlayerListViewController.swift
// SPPlayerDetailViewController.swift
// ShortPlay
//
// Created by on 2025/4/10.
@ -7,10 +7,10 @@
import UIKit
class SPTVPlayerListViewController: SPPlayerListViewController {
class SPPlayerDetailViewController: SPPlayerListViewController {
override var PlayerCellClass: SPPlayerListCell.Type {
return SPTVPlayerListCell.self
return SPPlayerDetailCell.self
}
override var contentSize: CGSize {
@ -23,12 +23,17 @@ class SPTVPlayerListViewController: SPPlayerListViewController {
private var detailModel: SPVideoDetailModel?
private weak var episodeView: SPEpisodeView?
override func viewDidLoad() {
super.viewDidLoad()
self.autoNextEpisode = true
self.dataSource = self
self.delegate = self
requestDetailData()
_addAction()
}
@ -49,11 +54,33 @@ class SPTVPlayerListViewController: SPPlayerListViewController {
}
extension SPPlayerDetailViewController {
private func _addAction() {
self.viewModel.handleEpisode = { [weak self] in
self?.onEpisode()
}
}
private func onEpisode() {
let view = SPEpisodeView()
view.dataArr = detailModel?.episodeList ?? []
view.shortModel = detailModel?.shortPlayInfo
view.currentIndex = self.currentIndexPath.row
view.didSelectedIndex = { [weak self] (index) in
self?.scrollToItem(indexPath: IndexPath(row: index, section: 0))
}
view.present(in: nil)
self.episodeView = view
}
}
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SPPlayerListViewControllerDelegate {
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
if let cell = oldCell as? SPPlayerListCell {
cell.model = detailModel
if let cell = oldCell as? SPPlayerDetailCell {
cell.shortModel = detailModel?.shortPlayInfo
cell.videoInfo = detailModel?.episodeList?[indexPath.row]
cell.isLoop = false
}
@ -63,10 +90,14 @@ extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
return detailModel?.episodeList?.count ?? 0
}
func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
self.episodeView?.currentIndex = indexPath.row
}
}
extension SPTVPlayerListViewController {
extension SPPlayerDetailViewController {
private func requestDetailData() {
guard let shortPlayId = self.shortPlayId else { return }

View File

@ -19,6 +19,8 @@ import UIKit
///
@objc optional func sp_playerViewControllerLoadUpMoreData(playerViewController: SPPlayerListViewController)
///
@objc optional func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath)
///
// @objc optional func yd_playerViewController(playerListViewController: BCListPlayerViewController, didShowPlayerPage playerViewController: YDBasePlayerViewController)
}
@ -31,6 +33,7 @@ import UIKit
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int
}
class SPPlayerListViewController: SPViewController {
@ -153,6 +156,10 @@ class SPPlayerListViewController: SPViewController {
func getDataCount() -> Int {
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
}
func scrollToItem(indexPath: IndexPath) {
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true);
}
}
extension SPPlayerListViewController {
@ -175,6 +182,7 @@ extension SPPlayerListViewController {
self.viewModel.handlePlayFinish = { [weak self] in
self?.currentPlayFinish()
}
}
}
@ -227,6 +235,8 @@ extension SPPlayerListViewController {
}
return true
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
@ -251,6 +261,7 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
if self.viewModel.currentPlayer == nil, indexPath == currentIndexPath, let playerProtocol = cell as? SPPlayerProtocol {
self.currentIndexPath = indexPath
self.viewModel.currentPlayer = playerProtocol
didChangeIndexPathForVisible()
}
return cell
@ -293,6 +304,7 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? SPPlayerProtocol else { return }
self.viewModel.currentPlayer = currentPlayer
// currentCell = self.collectionView.cellForItem(at: indexPath) as? BCListPlayerCell
didChangeIndexPathForVisible()
self.play()
}
@ -329,4 +341,8 @@ extension SPPlayerListViewController {
// }
self.delegate?.sp_playerViewControllerLoadUpMoreData?(playerViewController: self)
}
private func didChangeIndexPathForVisible() {
self.delegate?.sp_playerListViewController?(self, didChangeIndexPathForVisible: self.currentIndexPath)
}
}

View File

@ -0,0 +1,70 @@
//
// SPEpisodeCell.swift
// ShortPlay
//
// Created by on 2025/4/16.
//
import UIKit
class SPEpisodeCell: SPCollectionViewCell {
var videoInfoModel: SPVideoInfoModel? {
didSet {
textLabel.text = "\(videoInfoModel?.episode ?? 0)"
}
}
var sp_isSelected: Bool = false {
didSet {
textLabel.isHidden = sp_isSelected
playImageView.isHidden = !sp_isSelected
}
}
private lazy var textLabel: UILabel = {
let label = UILabel()
label.font = .fontLight(ofSize: 14)
label.textColor = .colorD2D2D2()
return label
}()
private lazy var playImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "play_icon_01"))
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .colorFFFFFF(alpha: 0.14)
contentView.layer.cornerRadius = 7
contentView.layer.masksToBounds = true
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPEpisodeCell {
private func _setupUI() {
contentView.addSubview(textLabel)
contentView.addSubview(playImageView)
textLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
playImageView.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
}

View File

@ -0,0 +1,153 @@
//
// SPEpisodeView.swift
// ShortPlay
//
// Created by on 2025/4/16.
//
import UIKit
class SPEpisodeView: HWPanModalContentView {
var currentIndex: Int = 0 {
didSet {
self.collectionView.reloadData()
}
}
var shortModel: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: shortModel?.image_url)
}
}
var dataArr: [SPVideoInfoModel] = [] {
didSet {
self.collectionView.reloadData()
}
}
var didSelectedIndex: ((_ index: Int) -> Void)?
//MARK: UI
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let itemWidth = floor((kSPScreenWidth - 10 * 4 - 30) / 5)
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: itemWidth, height: 50)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
return layout
}()
private lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
SPEpisodeCell.registerCell(collectionView: collectionView)
return collectionView
}()
private lazy var indicatorView: UIView = {
let view = UIView()
view.backgroundColor = .colorFFFFFF()
view.layer.cornerRadius = 2
view.layer.masksToBounds = true
return view
}()
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
imageView.layer.cornerRadius = 6
imageView.layer.masksToBounds = true
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: HWPanModalPresentable
override func panScrollable() -> UIScrollView? {
return collectionView
}
override func longFormHeight() -> PanModalHeight {
return PanModalHeightMake(.content, kSPScreenHeight * (2 / 3))
}
override func showDragIndicator() -> Bool {
return false
}
override func backgroundConfig() -> HWBackgroundConfig {
let config = HWBackgroundConfig()
config.backgroundAlpha = 0.4
return config
}
override func present(in view: UIView?) {
super.present(in: view)
self.hw_contentView.addEffectView(style: .dark)
}
}
extension SPEpisodeView {
private func _setupUI() {
addSubview(indicatorView)
addSubview(coverImageView)
addSubview(self.collectionView)
self.indicatorView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(15)
make.width.equalTo(30)
make.height.equalTo(4)
}
self.coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(39)
make.width.equalTo(55)
make.height.equalTo(74)
}
self.collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPEpisodeView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPEpisodeCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.videoInfoModel = self.dataArr[indexPath.row]
cell.sp_isSelected = indexPath.row == currentIndex
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataArr.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard indexPath.row != currentIndex else { return }
self.didSelectedIndex?(indexPath.row)
self.dismiss(animated: true) {
}
}
}

View File

@ -15,15 +15,20 @@ class SPPlayerControlView: UIView {
}
}
var model: Any? {
var shortModel: SPShortModel? {
didSet {
updateCollectButtonState()
}
}
var videoInfo: SPVideoInfoModel? {
didSet {
guard let model = model as? SPShortModel else { return }
}
}
///
var panProgressFinishBlock: ((_ progress: CGFloat) -> Void)?
@ -39,7 +44,8 @@ class SPPlayerControlView: UIView {
updatePlayIconState()
}
}
//MARK: UI
private(set) lazy var progressView: SPPlayerProgressView = {
let view = SPPlayerProgressView()
view.panStart = { [weak self] in
@ -65,17 +71,36 @@ class SPPlayerControlView: UIView {
return imageView
}()
///
private(set) lazy var rightFeatureView: UIStackView = {
let view = UIStackView(arrangedSubviews: [collectButton])
view.axis = .vertical
view.spacing = 25
return view
}()
///
private lazy var collectButton: UIButton = {
let button = createFeatureButton(title: "Save".localized, selectedTitle: "Added".localized, image: UIImage(named: "collect_icon_01"), selectedImage: UIImage(named: "collect_icon_01_selected"))
button.addTarget(self, action: #selector(handleCollectButton), for: .touchUpInside)
return button
}()
deinit {
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
NotificationCenter.default.removeObserver(self)
}
override init(frame: CGRect) {
super.init(frame: frame)
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: SPVideoAPI.updateShortCollectStateNotification, object: nil)
let tap = UITapGestureRecognizer(target: self, action: #selector(hadlePlayAndOrPaused))
self.addGestureRecognizer(tap)
sp_setupUI()
}
@ -89,6 +114,23 @@ class SPPlayerControlView: UIView {
fatalError("init(coder:) has not been implemented")
}
func createFeatureButton(title: String?, selectedTitle: String? = nil, image: UIImage?, selectedImage: UIImage? = nil) -> UIButton {
let button = JXButton(type: .custom)
button.titleDirection = .down
button.setImage(image, for: .normal)
button.setImage(selectedImage, for: .selected)
button.setImage(selectedImage, for: [.selected, .highlighted])
button.setTitle(title, for: .normal);
button.setTitle(selectedTitle, for: .selected);
button.setTitle(selectedTitle, for: [.selected, .highlighted])
button.setTitleColor(.colorFFFFFF(alpha: 0.9), for: .normal)
button.setTitleColor(.colorF564B6(), for: .selected)
button.jx_font = .fontLight(ofSize: 11);
return button
}
}
extension SPPlayerControlView {
@ -96,6 +138,7 @@ extension SPPlayerControlView {
private func sp_setupUI() {
addSubview(progressView)
addSubview(playImageView)
addSubview(rightFeatureView)
progressView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
@ -108,6 +151,11 @@ extension SPPlayerControlView {
make.center.equalToSuperview()
make.width.height.equalTo(100)
}
rightFeatureView.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.bottom.equalToSuperview().offset(-200)
}
}
}
@ -132,7 +180,29 @@ extension SPPlayerControlView {
// SPAPPTool.topViewController()?.navigationController?.pushViewController(vc, animated: true)
}
@objc private func handleCollectButton() {
guard let shortPlayId = self.videoInfo?.short_play_id else { return }
let isCollect = !(self.shortModel?.is_collect ?? false)
SPVideoAPI.requestCollectShort(isCollect: isCollect, shortPlayId: shortPlayId) {
}
}
@objc private func updateShortCollectStateNotification(sender: Notification) {
guard let userInfo = sender.userInfo else { return }
guard let shortPlayId = userInfo["id"] as? String else { return }
guard let isCollect = userInfo["state"] as? Bool else { return }
guard shortPlayId == self.videoInfo?.short_play_id else { return }
self.shortModel?.is_collect = isCollect;
updateCollectButtonState()
}
private func updateCollectButtonState() {
self.collectButton.isSelected = self.shortModel?.is_collect ?? false
}
}

View File

@ -0,0 +1,26 @@
//
// SPPlayerDetailCell.swift
// ShortPlay
//
// Created by on 2025/4/10.
//
import UIKit
class SPPlayerDetailCell: SPPlayerListCell {
override var PlayerControlViewClass: SPPlayerControlView.Type {
return SPPlayerDetailControlView.self
}
override init(frame: CGRect) {
super.init(frame: frame)
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,44 @@
//
// SPPlayerDetailControlView.swift
// ShortPlay
//
// Created by on 2025/4/16.
//
import UIKit
class SPPlayerDetailControlView: SPPlayerControlView {
private lazy var episodeButton: UIButton = {
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episodes_icon_01"))
button.addTarget(self, action: #selector(handleEpisodeButton), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPPlayerDetailControlView {
private func _setupUI() {
self.rightFeatureView.addArrangedSubview(episodeButton)
}
}
extension SPPlayerDetailControlView {
@objc private func handleEpisodeButton() {
self.viewModel?.handleEpisode?()
}
}

View File

@ -9,6 +9,10 @@ import UIKit
class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
var PlayerControlViewClass: SPPlayerControlView.Type {
return SPPlayerControlView.self
}
weak var viewModel: SPPlayerListViewModel? {
didSet {
controlView.viewModel = viewModel
@ -39,7 +43,7 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
}()
private lazy var controlView: SPPlayerControlView = {
let view = SPPlayerControlView()
let view = PlayerControlViewClass.init()
view.panProgressFinishBlock = { [weak self] progress in
guard let self = self else { return }
let duration = CGFloat(self.player.duration)
@ -65,19 +69,30 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
self.controlView.progress = 0
self.coverImageView.isHidden = false
if let model = model as? SPShortModel {
self.controlView.model = model
coverImageView.sp_setImage(url: model.image_url)
} else if let model = model as? SPVideoDetailModel {
self.controlView.model = model.shortPlayInfo
coverImageView.sp_setImage(url: model.shortPlayInfo?.image_url)
}
// if let model = model as? SPShortModel {
// self.controlView.model = model
// coverImageView.sp_setImage(url: model.image_url)
// } else if let model = model as? SPVideoDetailModel {
// self.controlView.model = model.shortPlayInfo
// coverImageView.sp_setImage(url: model.shortPlayInfo?.image_url)
// }
}
}
var shortModel: SPShortModel? {
didSet {
self.controlView.shortModel = shortModel
coverImageView.sp_setImage(url: shortModel?.image_url)
}
}
var videoInfo: SPVideoInfoModel? {
didSet {
self.controlView.progress = 0
self.coverImageView.isHidden = false
self.controlView.videoInfo = videoInfo
player.setPlayUrl(url: videoInfo?.video_url ?? "")
}
}

View File

@ -1,12 +0,0 @@
//
// SPTVPlayerListCell.swift
// ShortPlay
//
// Created by on 2025/4/10.
//
import UIKit
class SPTVPlayerListCell: SPPlayerListCell {
}

View File

@ -41,6 +41,7 @@ class SPPlayerListViewModel: NSObject {
var handlePauseOrPlay: (() -> Void)?
///
var handlePlayFinish: (() -> Void)?
///
var handleEpisode: (() -> Void)?
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "收藏@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "收藏@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "收藏@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "收藏@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Episodes@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Episodes@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play1@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play1@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -11,3 +11,4 @@
#import <Toast.h>
#import "NSUserDefaults+JXAdd.h"
#import <KTVHTTPCache.h>
#import <HWPanModal/HWPanModal.h>

View File

@ -17,3 +17,6 @@
"Trending Now" = "Trending Now";
"Editor's Hotlist" = "Editor's Hotlist";
"Shorts for You" = "Shorts for You";
"Episodes" = "Episodes";
"Save" = "Save";
"Added" = "Added";

View File

@ -0,0 +1,218 @@
//
// JXButton.swift
// BoJia
//
// Created by on 2024/5/31.
//
import UIKit
class JXButton: UIButton {
lazy var jx_font: UIFont? = self.titleLabel?.font {
didSet {
self.titleLabel?.font = jx_font
}
}
var maxTitleWidth: CGFloat = 0
var titleDirection: UITextLayoutDirection?
///
var leftAnyRightmargin: CGFloat = 0
///
var space: CGFloat = 0
private var imageRect: CGRect = .zero
private var titleRect: CGRect = .zero
override func layoutSubviews() {
super.layoutSubviews()
}
override var intrinsicContentSize: CGSize {
var width: CGFloat = 0
var height: CGFloat = 0
switch titleDirection {
case .left:
width = imageRect.width + titleRect.width + space
if imageRect.height > titleRect.height {
height = imageRect.height
} else {
height = titleRect.height
}
case .up:
if imageRect.width > titleRect.width {
width = imageRect.width
} else {
width = titleRect.width
}
height = titleRect.height + imageRect.height + space
case .down:
if imageRect.width > titleRect.width {
width = imageRect.width
} else {
width = titleRect.width
}
height = titleRect.height + imageRect.height + space
default:
width = imageRect.width + titleRect.width + space
if imageRect.height > titleRect.height {
height = imageRect.height
} else {
height = titleRect.height
}
}
let size = CGSize(width: width + leftAnyRightmargin * 2, height: height)
return size
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let imageSize = currentImage?.size ?? .zero
let textSize = self._textSize()
let contentWidth = self.frame.size.width
let contentHeight = self.frame.size.height
var x: CGFloat = 0
var y: CGFloat = 0
var width: CGFloat = 0
var height: CGFloat = 0
switch titleDirection {
case .left:
x = (contentWidth - space) / 2 - imageSize.width / 2 + textSize.width / 2 + space
y = contentHeight / 2 - imageSize.height / 2
width = imageSize.width
height = imageSize.height
case .up:
x = contentWidth / 2 - imageSize.width / 2
y = (contentHeight - space) / 2 - imageSize.height / 2 + textSize.height / 2 + space
width = imageSize.width
height = imageSize.height
case .down:
x = contentWidth / 2 - imageSize.width / 2
y = (contentHeight - space) / 2 - imageSize.height / 2 - textSize.height / 2
width = imageSize.width
height = imageSize.height
default:
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
y = contentHeight / 2 - imageSize.height / 2
width = imageSize.width
height = imageSize.height
}
self.imageRect = CGRect(x: x, y: y, width: width, height: height)
return self.imageRect
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let imageSize = currentImage?.size ?? .zero
let textSize = self._textSize()
let contentWidth = self.frame.size.width
let contentHeight = self.frame.size.height
var x: CGFloat = 0
var y: CGFloat = 0
var width: CGFloat = 0
var height: CGFloat = 0
switch titleDirection {
case .left:
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
y = contentHeight / 2 - textSize.height / 2
width = textSize.width
height = textSize.height
case .up:
x = contentWidth / 2 - textSize.width / 2
y = (contentHeight - space) / 2 - textSize.height / 2 - imageSize.height / 2
width = textSize.width
height = textSize.height
case .down:
x = contentWidth / 2 - textSize.width / 2
y = (contentHeight - space) / 2 - textSize.height / 2 + imageSize.height / 2 + space
width = textSize.width
height = textSize.height
default:
x = (contentWidth - space) / 2 + imageSize.width / 2 - textSize.width / 2 + space
y = contentHeight / 2 - textSize.height / 2
width = textSize.width
height = textSize.height
}
self.titleRect = CGRect(x: x, y: y, width: width, height: height)
return self.titleRect
}
private var borderColors: [UInt : UIColor] = [:]
func jx_setBorderColor(_ color: UIColor?, for state: UIControl.State) {
var arr = self.borderColors
let index = state.rawValue
arr[index] = color
if state == .selected {
let i = index + UIControl.State.highlighted.rawValue
arr[i] = color
}
self.borderColors = arr
updateStatus()
}
func updateStatus() {
var color = self.borderColors[self.state.rawValue]?.cgColor
if (color == nil) {
color = self.borderColors[UIControl.State.normal.rawValue]?.cgColor
}
self.layer.borderColor = color
}
override var isSelected: Bool {
didSet {
updateStatus()
}
}
}
extension JXButton {
private func _textSize() -> CGSize {
// let size = CGSize(width: self.bounds.size.width, height: self.bounds.size.height)
var attr: [NSAttributedString.Key : Any] = [:]
attr[NSAttributedString.Key.font] = jx_font
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byTruncatingMiddle
attr[NSAttributedString.Key.paragraphStyle] = paragraphStyle
// var rect = self.currentTitle?.ocString.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attr, context: nil) ?? .zero
if let font = jx_font {
var size = self.currentTitle?.size(font: font) ?? .zero
if maxTitleWidth != 0 && maxTitleWidth < size.width {
size = CGSize(width: maxTitleWidth, height: size.height)
}
// if maxTitleWidth != 0 && maxTitleWidth < rect.size.width {
// rect.size = CGSize(width: maxTitleWidth, height: rect.size.height)
// }
return size
} else {
return .zero
}
}
}