播放页面功能开发,我的页面开发
@ -14,7 +14,7 @@ class SPTabBarController: UITabBarController {
|
|||||||
|
|
||||||
let nav1 = createNavigationController(viewController: SPHomePageController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
|
let nav1 = createNavigationController(viewController: SPHomePageController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
|
||||||
|
|
||||||
let nav2 = createNavigationController(viewController: SPForYouViewController(), title: "For You".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
let nav2 = createNavigationController(viewController: SPExploreViewController(), title: "For You".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
||||||
|
|
||||||
let nav5 = createNavigationController(viewController: SPMineViewController(), title: "Profile".localized, image: UIImage(named: "tabbar_icon_05"), selectedImage: UIImage(named: "tabbar_icon_05_selected"))
|
let nav5 = createNavigationController(viewController: SPMineViewController(), title: "Profile".localized, image: UIImage(named: "tabbar_icon_05"), selectedImage: UIImage(named: "tabbar_icon_05_selected"))
|
||||||
|
|
||||||
|
30
ShortPlay/Base/Extension/Int+SPAdd.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// Int+SPAdd.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class Int_SPAdd: NSObject {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension Int {
|
||||||
|
|
||||||
|
func formatTimeGroup() -> (String, String, String) {
|
||||||
|
let seconds = self
|
||||||
|
|
||||||
|
var s: String = "00"
|
||||||
|
var m: String = "00"
|
||||||
|
var h: String = "00"
|
||||||
|
s = String(format: "%02d", Int(Int(seconds) % 60))
|
||||||
|
m = String(format: "%02d", Int(seconds / 60) % 60)
|
||||||
|
h = String(format: "%02d", Int(seconds / 3600))
|
||||||
|
|
||||||
|
return (h, m, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -64,6 +64,16 @@ extension UIColor {
|
|||||||
return color(hex: 0xF56490, alpha: alpha)
|
return color(hex: 0xF56490, alpha: alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func color9D9D9D(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0x9D9D9D, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func color545454(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0x545454, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func colorD568D2(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0xD568D2, alpha: alpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
ShortPlay/Base/View/SPScrollView.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// SPScrollView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPScrollView: UIScrollView {
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,7 +9,7 @@ import UIKit
|
|||||||
|
|
||||||
class SPTableView: UITableView {
|
class SPTableView: UITableView {
|
||||||
|
|
||||||
var insetGroupedMargins: CGFloat = 12
|
var insetGroupedMargins: CGFloat = 15
|
||||||
|
|
||||||
override init(frame: CGRect, style: UITableView.Style) {
|
override init(frame: CGRect, style: UITableView.Style) {
|
||||||
super.init(frame: frame, style: style)
|
super.init(frame: frame, style: style)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// SPForYouViewController.swift
|
// SPExploreViewController.swift
|
||||||
// ShortPlay
|
// ShortPlay
|
||||||
//
|
//
|
||||||
// Created by 曾觉新 on 2025/4/9.
|
// Created by 曾觉新 on 2025/4/9.
|
||||||
@ -7,7 +7,11 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SPForYouViewController: SPPlayerListViewController {
|
class SPExploreViewController: SPPlayerListViewController {
|
||||||
|
|
||||||
|
override var PlayerCellClass: SPPlayerListCell.Type {
|
||||||
|
return SPExplorePlayerCell.self
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
@ -27,7 +31,7 @@ class SPForYouViewController: SPPlayerListViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//MARK: -------------- SPPlayerListViewControllerDelegate --------------
|
//MARK: -------------- SPPlayerListViewControllerDelegate --------------
|
||||||
extension SPForYouViewController: SPPlayerListViewControllerDelegate {
|
extension SPExploreViewController: SPPlayerListViewControllerDelegate {
|
||||||
func sp_playerViewControllerLoadMoreData(playerViewController: SPPlayerListViewController) {
|
func sp_playerViewControllerLoadMoreData(playerViewController: SPPlayerListViewController) {
|
||||||
guard let pagination = self.pagination else { return }
|
guard let pagination = self.pagination else { return }
|
||||||
guard let page = self.pagination?.current_page else { return }
|
guard let page = self.pagination?.current_page else { return }
|
||||||
@ -40,7 +44,7 @@ extension SPForYouViewController: SPPlayerListViewControllerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
|
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
|
||||||
extension SPForYouViewController: SPPlayerListViewControllerDataSource {
|
extension SPExploreViewController: SPPlayerListViewControllerDataSource {
|
||||||
|
|
||||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
||||||
|
|
||||||
@ -58,7 +62,7 @@ extension SPForYouViewController: SPPlayerListViewControllerDataSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SPForYouViewController {
|
extension SPExploreViewController {
|
||||||
|
|
||||||
private func requestDataArr(page: Int) {
|
private func requestDataArr(page: Int) {
|
||||||
|
|
16
ShortPlay/Class/Explore/View/SPExplorePlayerCell.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// SPExplorePlayerCell.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPExplorePlayerCell: SPPlayerListCell {
|
||||||
|
|
||||||
|
override var PlayerControlViewClass: SPPlayerControlView.Type {
|
||||||
|
return SPExplorePlayerControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// SPExplorePlayerControlView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPExplorePlayerControlView: SPPlayerControlView {
|
||||||
|
|
||||||
|
|
||||||
|
override var shortModel: SPShortModel? {
|
||||||
|
didSet {
|
||||||
|
desLabel.text = shortModel?.sp_description
|
||||||
|
videoInfoView.shortModel = shortModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 12)
|
||||||
|
label.textColor = .colorD2D2D2()
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var videoInfoView: SPVideoPlayerInfoView = {
|
||||||
|
let view = SPVideoPlayerInfoView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.progressView.isHidden = true
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPExplorePlayerControlView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(desLabel)
|
||||||
|
addSubview(videoInfoView)
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-30)
|
||||||
|
make.bottom.equalToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
videoInfoView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.bottom.equalTo(desLabel.snp.top).offset(-10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
ShortPlay/Class/Explore/View/SPVideoPlayerInfoView.swift
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
//
|
||||||
|
// SPVideoPlayerInfoView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPVideoPlayerInfoView: UIView {
|
||||||
|
|
||||||
|
var shortModel: SPShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||||
|
titleLabel.text = shortModel?.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var bgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.layer.cornerRadius = 5
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.backgroundColor = .color000000(alpha: 0.27)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var coverImageView: SPImageView = {
|
||||||
|
let imageView = SPImageView()
|
||||||
|
imageView.layer.cornerRadius = 5
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 13)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var moreButton: JXButton = {
|
||||||
|
let button = JXButton(type: .custom)
|
||||||
|
button.colors = [UIColor.colorF56490().cgColor, UIColor.colorD568D2().cgColor]
|
||||||
|
button.startPoint = .init(x: 0, y: 0.5)
|
||||||
|
button.endPoint = .init(x: 1, y: 0.5)
|
||||||
|
button.locations = [0, 1]
|
||||||
|
button.setImage(UIImage(named: "play_icon_02"), for: .normal)
|
||||||
|
button.setTitle("Series".localized, for: .normal)
|
||||||
|
button.setTitleColor(.colorFFFFFF(), for: .normal)
|
||||||
|
button.jx_font = .fontRegular(ofSize: 11)
|
||||||
|
button.layer.cornerRadius = 10.5
|
||||||
|
button.layer.masksToBounds = true
|
||||||
|
button.space = 2
|
||||||
|
button.addTarget(self, action: #selector(handleMoreButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleMoreButton() {
|
||||||
|
let vc = SPPlayerDetailViewController()
|
||||||
|
vc.shortPlayId = shortModel?.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPVideoPlayerInfoView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(bgView)
|
||||||
|
addSubview(coverImageView)
|
||||||
|
addSubview(titleLabel)
|
||||||
|
addSubview(moreButton)
|
||||||
|
|
||||||
|
bgView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.width.equalTo(240)
|
||||||
|
make.height.equalTo(54)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.left.bottom.top.equalToSuperview()
|
||||||
|
make.width.equalTo(49)
|
||||||
|
make.height.equalTo(66)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||||
|
make.top.equalTo(bgView).offset(5)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-18)
|
||||||
|
}
|
||||||
|
|
||||||
|
moreButton.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||||
|
make.bottom.equalToSuperview().offset(-5)
|
||||||
|
make.width.equalTo(59)
|
||||||
|
make.height.equalTo(21)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,15 +9,87 @@ import UIKit
|
|||||||
|
|
||||||
class SPMineViewController: SPViewController {
|
class SPMineViewController: SPViewController {
|
||||||
|
|
||||||
|
private lazy var dataArr: [SPMineItem] = {
|
||||||
|
let arr = [
|
||||||
|
SPMineItem(type: .orderRecord, iconImage: UIImage(named: "order_record_icon_01"), title: "Order Record".localized),
|
||||||
|
SPMineItem(type: .language, iconImage: UIImage(named: "language_icon_01"), title: "Language".localized),
|
||||||
|
SPMineItem(type: .privacyPolicy, iconImage: UIImage(named: "privacy_policy_icon_01"), title: "Privacy Policy".localized),
|
||||||
|
SPMineItem(type: .userAgreement, iconImage: UIImage(named: "user_agreement_icon_01"), title: "User Agreement".localized),
|
||||||
|
SPMineItem(type: .helpCenter, iconImage: UIImage(named: "help_center_icon_01"), title: "Help Center".localized),
|
||||||
|
SPMineItem(type: .aboutUs, iconImage: UIImage(named: "about_us_icon_01"), title: "About Us".localized),
|
||||||
|
]
|
||||||
|
return arr
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var tableView: SPTableView = {
|
||||||
|
let tableView = SPTableView(frame: .zero, style: .insetGrouped)
|
||||||
|
tableView.delegate = self
|
||||||
|
tableView.dataSource = self
|
||||||
|
tableView.rowHeight = 50
|
||||||
|
SPMineCell.registerCell(tableView: tableView)
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.setNavigationNormalStyle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPMineViewController {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
view.addSubview(tableView)
|
||||||
|
|
||||||
|
tableView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UITableViewDelegate & UITableViewDataSource --------------
|
||||||
|
extension SPMineViewController: UITableViewDelegate, UITableViewDataSource {
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = SPMineCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
|
||||||
|
cell.item = dataArr[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
let item = dataArr[indexPath.row]
|
||||||
|
switch item.type {
|
||||||
|
case .privacyPolicy:
|
||||||
|
let vc = SPWebViewController()
|
||||||
|
vc.urlStr = SPPrivacyPolicyWebUrl
|
||||||
|
self.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
|
||||||
|
case .userAgreement:
|
||||||
|
let vc = SPWebViewController()
|
||||||
|
vc.urlStr = SPUserAgreementWebUrl
|
||||||
|
self.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPMineViewController {
|
||||||
|
|
||||||
|
// func
|
||||||
|
|
||||||
}
|
}
|
||||||
|
32
ShortPlay/Class/Mine/Model/SPMineItem.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// SPMineItem.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct SPMineItem {
|
||||||
|
enum ItemType {
|
||||||
|
///历史订单
|
||||||
|
case orderRecord
|
||||||
|
///语言
|
||||||
|
case language
|
||||||
|
///隐私协议
|
||||||
|
case privacyPolicy
|
||||||
|
///用户协议
|
||||||
|
case userAgreement
|
||||||
|
///帮助中心
|
||||||
|
case helpCenter
|
||||||
|
///关于我们
|
||||||
|
case aboutUs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var type: ItemType?
|
||||||
|
var iconImage: UIImage?
|
||||||
|
var title: String?
|
||||||
|
|
||||||
|
|
||||||
|
}
|
64
ShortPlay/Class/Mine/View/SPMineCell.swift
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// SPMineCell.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPMineCell: SPTableViewCell {
|
||||||
|
|
||||||
|
var item: SPMineItem? {
|
||||||
|
didSet {
|
||||||
|
iconImageView.image = item?.iconImage
|
||||||
|
titleLabel.text = item?.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var iconImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 14)
|
||||||
|
label.textColor = .colorFFFFFF(alpha: 0.9)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
|
self.contentView.backgroundColor = .colorFFFFFF(alpha: 0.04)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPMineCell {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
contentView.addSubview(iconImageView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
|
||||||
|
iconImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,8 +23,24 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
|
|||||||
|
|
||||||
private var detailModel: SPVideoDetailModel?
|
private var detailModel: SPVideoDetailModel?
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
///选集视图
|
||||||
private weak var episodeView: SPEpisodeView?
|
private weak var episodeView: SPEpisodeView?
|
||||||
|
|
||||||
|
private lazy var backButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
||||||
|
button.addTarget(self, action: #selector(handleBack), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontLight(ofSize: 15)
|
||||||
|
label.textColor = .colorFFFFFF(alpha: 0.9)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.autoNextEpisode = true
|
self.autoNextEpisode = true
|
||||||
@ -34,6 +50,8 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
|
|||||||
requestDetailData()
|
requestDetailData()
|
||||||
|
|
||||||
_addAction()
|
_addAction()
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -56,11 +74,33 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
|
|||||||
|
|
||||||
extension SPPlayerDetailViewController {
|
extension SPPlayerDetailViewController {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
view.addSubview(backButton)
|
||||||
|
view.addSubview(titleLabel)
|
||||||
|
|
||||||
|
backButton.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(5)
|
||||||
|
make.top.equalToSuperview().offset(5 + kSPStatusbarHeight)
|
||||||
|
make.width.height.equalTo(37)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(backButton.snp.right)
|
||||||
|
make.centerY.equalTo(backButton)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func _addAction() {
|
private func _addAction() {
|
||||||
self.viewModel.handleEpisode = { [weak self] in
|
self.viewModel.handleEpisode = { [weak self] in
|
||||||
self?.onEpisode()
|
self?.onEpisode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPPlayerDetailViewController {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private func onEpisode() {
|
private func onEpisode() {
|
||||||
let view = SPEpisodeView()
|
let view = SPEpisodeView()
|
||||||
@ -93,6 +133,9 @@ extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SP
|
|||||||
|
|
||||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
|
func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
|
||||||
self.episodeView?.currentIndex = indexPath.row
|
self.episodeView?.currentIndex = indexPath.row
|
||||||
|
let videoInfo = detailModel?.episodeList?[indexPath.row]
|
||||||
|
|
||||||
|
titleLabel.text = String(format: "kPlayerDetailTitleString".localized, "\(videoInfo?.episode ?? 0)", self.detailModel?.shortPlayInfo?.name ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,19 +7,20 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct SPSpeedModel {
|
class SPSpeedModel: NSObject {
|
||||||
|
|
||||||
enum Speed: String {
|
enum Speed: String {
|
||||||
case x0_75 = "0.75x"
|
case x0_5 = "0.5x"
|
||||||
case x1 = "1.0x"
|
case x1 = "1.0x"
|
||||||
case x1_25 = "1.25x"
|
case x1_25 = "1.25x"
|
||||||
case x1_5 = "1.5x"
|
case x1_5 = "1.5x"
|
||||||
|
case x1_75 = "1.75x"
|
||||||
case x2 = "2.0x"
|
case x2 = "2.0x"
|
||||||
|
|
||||||
func getRate() -> Float {
|
func getRate() -> Float {
|
||||||
switch self {
|
switch self {
|
||||||
case .x0_75:
|
case .x0_5:
|
||||||
return 0.75
|
return 0.5
|
||||||
|
|
||||||
case .x1:
|
case .x1:
|
||||||
return 1
|
return 1
|
||||||
@ -30,12 +31,53 @@ struct SPSpeedModel {
|
|||||||
case .x1_5:
|
case .x1_5:
|
||||||
return 1.5
|
return 1.5
|
||||||
|
|
||||||
|
case .x1_75:
|
||||||
|
return 1.75
|
||||||
|
|
||||||
case .x2:
|
case .x2:
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func getAllSpeed() -> [SPSpeedModel] {
|
||||||
|
return [
|
||||||
|
SPSpeedModel(speed: .x0_5),
|
||||||
|
SPSpeedModel(speed: .x1),
|
||||||
|
SPSpeedModel(speed: .x1_25),
|
||||||
|
SPSpeedModel(speed: .x1_5),
|
||||||
|
SPSpeedModel(speed: .x1_75),
|
||||||
|
SPSpeedModel(speed: .x2)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
var speed: Speed = .x1
|
var speed: Speed = .x1
|
||||||
|
|
||||||
|
init(speed: Speed) {
|
||||||
|
super.init()
|
||||||
|
self.speed = speed
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRate() -> Float {
|
||||||
|
return speed.getRate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatString() -> String {
|
||||||
|
switch speed {
|
||||||
|
case .x0_5:
|
||||||
|
return "0.5x"
|
||||||
|
case .x1:
|
||||||
|
return "1.0x"
|
||||||
|
case .x1_25:
|
||||||
|
return "1.25x"
|
||||||
|
case .x1_5:
|
||||||
|
return "1.5x"
|
||||||
|
case .x1_75:
|
||||||
|
return "1.75x"
|
||||||
|
case .x2:
|
||||||
|
return "2.0x"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
149
ShortPlay/Class/Player/View/SPEpisodeMenuView.swift
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
//
|
||||||
|
// SPEpisodeMenuView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPEpisodeMenuView: UIView {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return CGSize(width: kSPScreenWidth, height: 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedIndex: ((_ index: Int) -> Void)?
|
||||||
|
|
||||||
|
var dataArr: [String] = [] {
|
||||||
|
didSet {
|
||||||
|
self.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedIndex: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
self.buttonArr.forEach {
|
||||||
|
$0.isSelected = $0.tag == selectedIndex
|
||||||
|
}
|
||||||
|
self.progressSlide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var buttonArr: [UIButton] = []
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var progressView: SPGradientView = {
|
||||||
|
let view = SPGradientView()
|
||||||
|
view.colors = [UIColor.colorF56490().cgColor, UIColor.colorBF6BFF().cgColor]
|
||||||
|
view.startPoint = .init(x: 0, y: 0.5)
|
||||||
|
view.endPoint = .init(x: 1, y: 0.5)
|
||||||
|
view.locations = [0, 1]
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var scrollView: SPScrollView = {
|
||||||
|
let scrollView = SPScrollView()
|
||||||
|
scrollView.showsVerticalScrollIndicator = false
|
||||||
|
scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
return scrollView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadData() {
|
||||||
|
buttonArr.forEach {
|
||||||
|
$0.removeFromSuperview()
|
||||||
|
}
|
||||||
|
buttonArr.removeAll()
|
||||||
|
|
||||||
|
let count = self.dataArr.count
|
||||||
|
|
||||||
|
var previousButton: UIButton?
|
||||||
|
|
||||||
|
dataArr.enumerated().forEach {
|
||||||
|
let normalStrig = NSMutableAttributedString(string: $1)
|
||||||
|
normalStrig.color = .color9D9D9D()
|
||||||
|
normalStrig.font = .fontLight(ofSize: 14)
|
||||||
|
|
||||||
|
let selectedString = NSMutableAttributedString(string: $1)
|
||||||
|
selectedString.color = .colorF564B6()
|
||||||
|
selectedString.font = .fontMedium(ofSize: 14)
|
||||||
|
|
||||||
|
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.tag = $0
|
||||||
|
button.setAttributedTitle(normalStrig, for: .normal)
|
||||||
|
button.setAttributedTitle(selectedString, for: .selected)
|
||||||
|
button.setAttributedTitle(selectedString, for: [.selected, .highlighted])
|
||||||
|
button.addTarget(self, action: #selector(handleButton), for: .touchUpInside)
|
||||||
|
button.isSelected = $0 == selectedIndex
|
||||||
|
|
||||||
|
self.scrollView.addSubview(button)
|
||||||
|
self.buttonArr.append(button)
|
||||||
|
|
||||||
|
if previousButton == nil {
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.left.equalToSuperview()
|
||||||
|
make.height.equalTo(32)
|
||||||
|
}
|
||||||
|
} else if let previousButton = previousButton, count - 1 == $0 {
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.left.equalTo(previousButton.snp.right).offset(25)
|
||||||
|
make.height.equalTo(32)
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
}
|
||||||
|
} else if let previousButton = previousButton {
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.left.equalTo(previousButton.snp.right).offset(25)
|
||||||
|
make.height.equalTo(32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousButton = button
|
||||||
|
}
|
||||||
|
|
||||||
|
progressSlide()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func progressSlide() {
|
||||||
|
if self.selectedIndex >= self.buttonArr.count { return }
|
||||||
|
let currentButton = self.buttonArr[self.selectedIndex]
|
||||||
|
|
||||||
|
self.progressView.snp.remakeConstraints { make in
|
||||||
|
make.bottom.width.equalTo(currentButton)
|
||||||
|
make.centerX.equalTo(currentButton)
|
||||||
|
make.height.equalTo(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@objc private func handleButton(sender: UIButton) {
|
||||||
|
self.selectedIndex = sender.tag
|
||||||
|
self.didSelectedIndex?(self.selectedIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPEpisodeMenuView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(scrollView)
|
||||||
|
scrollView.addSubview(progressView)
|
||||||
|
|
||||||
|
scrollView.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,17 +18,49 @@ class SPEpisodeView: HWPanModalContentView {
|
|||||||
var shortModel: SPShortModel? {
|
var shortModel: SPShortModel? {
|
||||||
didSet {
|
didSet {
|
||||||
coverImageView.sp_setImage(url: shortModel?.image_url)
|
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||||
|
titleLabel.text = shortModel?.name
|
||||||
|
desLabel.text = shortModel?.sp_description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataArr: [SPVideoInfoModel] = [] {
|
var dataArr: [SPVideoInfoModel] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
self.collectionView.reloadData()
|
self.collectionView.reloadData()
|
||||||
|
|
||||||
|
var menuDataArr = [String]()
|
||||||
|
let totalEpisode = dataArr.count
|
||||||
|
var index = 0
|
||||||
|
var remainingEpisodes = totalEpisode
|
||||||
|
|
||||||
|
while remainingEpisodes > 0 {
|
||||||
|
let minIndex = index * 30
|
||||||
|
var maxIndex = minIndex + 29
|
||||||
|
if maxIndex >= dataArr.count {
|
||||||
|
maxIndex = dataArr.count - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let minEpisode = dataArr[minIndex].episode ?? 0
|
||||||
|
let maxEpisode = dataArr[maxIndex].episode ?? 0
|
||||||
|
|
||||||
|
if minEpisode == maxEpisode {
|
||||||
|
menuDataArr.append("\(minEpisode)")
|
||||||
|
} else {
|
||||||
|
menuDataArr.append("\(minEpisode)-\(maxEpisode)")
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingEpisodes -= 30
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
self.menuView.dataArr = menuDataArr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var didSelectedIndex: ((_ index: Int) -> Void)?
|
var didSelectedIndex: ((_ index: Int) -> Void)?
|
||||||
|
|
||||||
|
var isDecelerating = false
|
||||||
|
var isDragging = false
|
||||||
|
|
||||||
//MARK: UI属性
|
//MARK: UI属性
|
||||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
let itemWidth = floor((kSPScreenWidth - 10 * 4 - 30) / 5)
|
let itemWidth = floor((kSPScreenWidth - 10 * 4 - 30) / 5)
|
||||||
@ -64,6 +96,46 @@ class SPEpisodeView: HWPanModalContentView {
|
|||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontMedium(ofSize: 15)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontLight(ofSize: 12)
|
||||||
|
label.textColor = .color9D9D9D()
|
||||||
|
label.numberOfLines = 0
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var lineView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .color545454()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var menuView: SPEpisodeMenuView = {
|
||||||
|
let view = SPEpisodeMenuView()
|
||||||
|
view.didSelectedIndex = { [weak self] index in
|
||||||
|
guard let self = self else { return }
|
||||||
|
var row = 0
|
||||||
|
if index > 0 {
|
||||||
|
row = index * 30 + 10
|
||||||
|
let count = self.dataArr.count
|
||||||
|
if row >= count {
|
||||||
|
row = count - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let indexPath = IndexPath.init(row: row, section: 0)
|
||||||
|
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
|
||||||
|
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
_setupUI()
|
_setupUI()
|
||||||
@ -107,7 +179,11 @@ extension SPEpisodeView {
|
|||||||
private func _setupUI() {
|
private func _setupUI() {
|
||||||
addSubview(indicatorView)
|
addSubview(indicatorView)
|
||||||
addSubview(coverImageView)
|
addSubview(coverImageView)
|
||||||
addSubview(self.collectionView)
|
addSubview(titleLabel)
|
||||||
|
addSubview(desLabel)
|
||||||
|
addSubview(menuView)
|
||||||
|
addSubview(lineView)
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
self.indicatorView.snp.makeConstraints { make in
|
self.indicatorView.snp.makeConstraints { make in
|
||||||
make.centerX.equalToSuperview()
|
make.centerX.equalToSuperview()
|
||||||
@ -123,8 +199,34 @@ extension SPEpisodeView {
|
|||||||
make.height.equalTo(74)
|
make.height.equalTo(74)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(self.coverImageView.snp.right).offset(12)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
make.centerY.equalTo(self.coverImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(self.coverImageView)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
make.top.equalTo(self.coverImageView.snp.bottom).offset(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.menuView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalTo(self.lineView)
|
||||||
|
make.bottom.equalTo(self.lineView)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lineView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.top.equalTo(self.desLabel.snp.bottom).offset(46)
|
||||||
|
make.height.equalTo(0.7)
|
||||||
|
}
|
||||||
|
|
||||||
self.collectionView.snp.makeConstraints { make in
|
self.collectionView.snp.makeConstraints { make in
|
||||||
make.edges.equalToSuperview()
|
// make.edges.equalToSuperview()
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(self.lineView.snp.bottom).offset(15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,4 +252,50 @@ extension SPEpisodeView: UICollectionViewDelegate, UICollectionViewDataSource {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
if isDragging || isDecelerating {
|
||||||
|
updateMuneSelectedIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
isDecelerating = false
|
||||||
|
updateMuneSelectedIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
isDecelerating = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
isDragging = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
|
isDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func updateMuneSelectedIndex() {
|
||||||
|
let indexPathArr = collectionView.indexPathsForVisibleItems
|
||||||
|
|
||||||
|
var minRow = dataArr.count - 1
|
||||||
|
var maxRow = 0
|
||||||
|
|
||||||
|
for indexPath in indexPathArr {
|
||||||
|
if indexPath.row < minRow {
|
||||||
|
minRow = indexPath.row
|
||||||
|
}
|
||||||
|
if indexPath.row > maxRow {
|
||||||
|
maxRow = indexPath.row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedIndex = maxRow / 30
|
||||||
|
if menuView.selectedIndex != selectedIndex {
|
||||||
|
menuView.selectedIndex = selectedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,9 @@ class SPPlayerControlView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var durationTime: Int = 0
|
||||||
|
var currentTime: Int = 0
|
||||||
|
|
||||||
var isCurrent: Bool = false {
|
var isCurrent: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updatePlayIconState()
|
updatePlayIconState()
|
||||||
@ -46,6 +49,17 @@ class SPPlayerControlView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//MARK: UI属性
|
//MARK: UI属性
|
||||||
|
///底部渐变层
|
||||||
|
private lazy var bottomGradientView: SPGradientView = {
|
||||||
|
let view = SPGradientView()
|
||||||
|
view.colors = [UIColor.color000000(alpha: 0).cgColor, UIColor.color000000(alpha: 0.5).cgColor]
|
||||||
|
view.startPoint = .init(x: 0.5, y: 0)
|
||||||
|
view.endPoint = .init(x: 0.5, y: 1)
|
||||||
|
view.locations = [0, 1]
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
private(set) lazy var progressView: SPPlayerProgressView = {
|
private(set) lazy var progressView: SPPlayerProgressView = {
|
||||||
let view = SPPlayerProgressView()
|
let view = SPPlayerProgressView()
|
||||||
view.panStart = { [weak self] in
|
view.panStart = { [weak self] in
|
||||||
@ -97,6 +111,7 @@ class SPPlayerControlView: UIView {
|
|||||||
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: SPVideoAPI.updateShortCollectStateNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: SPVideoAPI.updateShortCollectStateNotification, object: nil)
|
||||||
|
|
||||||
let tap = UITapGestureRecognizer(target: self, action: #selector(hadlePlayAndOrPaused))
|
let tap = UITapGestureRecognizer(target: self, action: #selector(hadlePlayAndOrPaused))
|
||||||
|
tap.delegate = self
|
||||||
self.addGestureRecognizer(tap)
|
self.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
|
||||||
@ -136,10 +151,16 @@ class SPPlayerControlView: UIView {
|
|||||||
extension SPPlayerControlView {
|
extension SPPlayerControlView {
|
||||||
|
|
||||||
private func sp_setupUI() {
|
private func sp_setupUI() {
|
||||||
|
addSubview(bottomGradientView)
|
||||||
addSubview(progressView)
|
addSubview(progressView)
|
||||||
addSubview(playImageView)
|
addSubview(playImageView)
|
||||||
addSubview(rightFeatureView)
|
addSubview(rightFeatureView)
|
||||||
|
|
||||||
|
bottomGradientView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.height.equalTo(kSPTabbarSafeBottomMargin + 200)
|
||||||
|
}
|
||||||
|
|
||||||
progressView.snp.makeConstraints { make in
|
progressView.snp.makeConstraints { make in
|
||||||
make.left.equalToSuperview().offset(10)
|
make.left.equalToSuperview().offset(10)
|
||||||
make.centerX.equalToSuperview()
|
make.centerX.equalToSuperview()
|
||||||
@ -236,3 +257,15 @@ extension SPPlayerControlView {
|
|||||||
self.panProgressFinishBlock?(progress)
|
self.panProgressFinishBlock?(progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UIGestureRecognizerDelegate --------------
|
||||||
|
extension SPPlayerControlView: UIGestureRecognizerDelegate {
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||||
|
if touch.view != self {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,12 +10,74 @@ import UIKit
|
|||||||
class SPPlayerDetailControlView: SPPlayerControlView {
|
class SPPlayerDetailControlView: SPPlayerControlView {
|
||||||
|
|
||||||
|
|
||||||
|
override var durationTime: Int {
|
||||||
|
didSet {
|
||||||
|
updateProgressTimeLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var currentTime: Int {
|
||||||
|
didSet {
|
||||||
|
updateProgressTimeLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var viewModel: SPPlayerListViewModel? {
|
||||||
|
didSet {
|
||||||
|
self.viewModel?.addObserver(self, forKeyPath: "speedModel", context: nil)
|
||||||
|
updateSpeedButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isCurrent: Bool {
|
||||||
|
didSet {
|
||||||
|
if isCurrent {
|
||||||
|
showSpeedSelectedView(isShow: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
private lazy var episodeButton: UIButton = {
|
private lazy var episodeButton: UIButton = {
|
||||||
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episodes_icon_01"))
|
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episodes_icon_01"))
|
||||||
button.addTarget(self, action: #selector(handleEpisodeButton), for: .touchUpInside)
|
button.addTarget(self, action: #selector(handleEpisodeButton), for: .touchUpInside)
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private lazy var progressTimeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontLight(ofSize: 12)
|
||||||
|
label.textColor = .colorFFFFFF(alpha: 0.9)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var speedButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setBackgroundImage(UIImage(color: .colorFFFFFF(alpha: 0.2)), for: .normal)
|
||||||
|
button.setTitleColor(.colorFFFFFF(alpha: 0.9), for: .normal)
|
||||||
|
button.titleLabel?.font = .fontLight(ofSize: 11)
|
||||||
|
button.layer.cornerRadius = 10
|
||||||
|
button.layer.masksToBounds = true
|
||||||
|
button.addTarget(self, action: #selector(handleSpeedButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var speedSelectedView: SPSpeedSelectedView = {
|
||||||
|
let view = SPSpeedSelectedView()
|
||||||
|
view.isHidden = true
|
||||||
|
view.didSelectedSpeed = { [weak self] speedModel in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel?.setSpeedPlay(speedModel: speedModel)
|
||||||
|
self.showSpeedSelectedView(isShow: false)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.viewModel?.removeObserver(self, forKeyPath: "speedModel")
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
@ -26,6 +88,25 @@ class SPPlayerDetailControlView: SPPlayerControlView {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||||
|
if keyPath == "speedModel" {
|
||||||
|
updateSpeedButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateProgressTimeLabel() {
|
||||||
|
let currentTime = self.currentTime.formatTimeGroup()
|
||||||
|
let durationTime = self.durationTime.formatTimeGroup()
|
||||||
|
|
||||||
|
progressTimeLabel.text = "\(currentTime.1):\(currentTime.2)/\(durationTime.1):\(durationTime.2)"
|
||||||
|
}
|
||||||
|
|
||||||
|
///更新速率按钮
|
||||||
|
private func updateSpeedButton() {
|
||||||
|
self.speedButton.setTitle(self.viewModel?.speedModel.formatString(), for: .normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SPPlayerDetailControlView {
|
extension SPPlayerDetailControlView {
|
||||||
@ -33,6 +114,34 @@ extension SPPlayerDetailControlView {
|
|||||||
private func _setupUI() {
|
private func _setupUI() {
|
||||||
self.rightFeatureView.addArrangedSubview(episodeButton)
|
self.rightFeatureView.addArrangedSubview(episodeButton)
|
||||||
|
|
||||||
|
addSubview(progressTimeLabel)
|
||||||
|
addSubview(speedButton)
|
||||||
|
addSubview(speedSelectedView)
|
||||||
|
|
||||||
|
self.progressView.snp.remakeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.height.equalTo(30)
|
||||||
|
make.bottom.equalToSuperview().offset(-(kSPTabbarSafeBottomMargin + 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.progressTimeLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(self.progressView)
|
||||||
|
make.bottom.equalTo(self.progressView).offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
speedButton.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(self.progressTimeLabel)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.width.equalTo(40)
|
||||||
|
make.height.equalTo(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
speedSelectedView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalTo(self.speedButton.snp.top).offset(-30)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -41,4 +150,18 @@ extension SPPlayerDetailControlView {
|
|||||||
@objc private func handleEpisodeButton() {
|
@objc private func handleEpisodeButton() {
|
||||||
self.viewModel?.handleEpisode?()
|
self.viewModel?.handleEpisode?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handleSpeedButton() {
|
||||||
|
showSpeedSelectedView(isShow: speedSelectedView.isHidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
///暂时或隐藏速率选择页面
|
||||||
|
private func showSpeedSelectedView(isShow: Bool) {
|
||||||
|
speedSelectedView.isHidden = !isShow
|
||||||
|
rightFeatureView.isHidden = isShow
|
||||||
|
|
||||||
|
if isShow {
|
||||||
|
self.speedSelectedView.currentSpeed = self.viewModel?.speedModel.speed ?? .x1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,9 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
|||||||
didSet {
|
didSet {
|
||||||
self.controlView.progress = 0
|
self.controlView.progress = 0
|
||||||
self.coverImageView.isHidden = false
|
self.coverImageView.isHidden = false
|
||||||
|
self.controlView.currentTime = 0
|
||||||
|
self.controlView.durationTime = 0
|
||||||
|
|
||||||
self.controlView.videoInfo = videoInfo
|
self.controlView.videoInfo = videoInfo
|
||||||
|
|
||||||
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
||||||
@ -169,6 +172,8 @@ extension SPPlayerListCell: SPPlayerDelegate {
|
|||||||
|
|
||||||
func sp_playTimeChanged(_ player: SPPlayer, currentTime: Int, duration: Int) {
|
func sp_playTimeChanged(_ player: SPPlayer, currentTime: Int, duration: Int) {
|
||||||
controlView.progress = CGFloat(currentTime) / CGFloat(duration)
|
controlView.progress = CGFloat(currentTime) / CGFloat(duration)
|
||||||
|
controlView.currentTime = currentTime
|
||||||
|
controlView.durationTime = duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func sp_firstRenderedStart(_ player: SPPlayer) {
|
func sp_firstRenderedStart(_ player: SPPlayer) {
|
||||||
|
@ -32,10 +32,10 @@ class SPPlayerProgressView: UIView {
|
|||||||
///滑动进度
|
///滑动进度
|
||||||
private var panProgress: CGFloat = 0
|
private var panProgress: CGFloat = 0
|
||||||
|
|
||||||
var progressColor: UIColor = .red
|
var progressColor: UIColor = .colorFFFFFF(alpha: 0.12)
|
||||||
var currentProgress: UIColor = .white
|
var currentProgress: UIColor = .colorFFFFFF(alpha: 0.48)
|
||||||
|
|
||||||
var lineWidth: CGFloat = 2
|
var lineWidth: CGFloat = 5
|
||||||
|
|
||||||
///是否在滑动中
|
///是否在滑动中
|
||||||
private var isPaning: Bool = false
|
private var isPaning: Bool = false
|
||||||
|
56
ShortPlay/Class/Player/View/SPSpeedSelectedCell.swift
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// SPSpeedSelectedCell.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPSpeedSelectedCell: SPCollectionViewCell {
|
||||||
|
|
||||||
|
var model: SPSpeedModel? {
|
||||||
|
didSet {
|
||||||
|
textLabel.text = model?.formatString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sp_isSelected: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if sp_isSelected {
|
||||||
|
self.contentView.layer.borderColor = UIColor.colorF564B6().cgColor
|
||||||
|
self.contentView.backgroundColor = .colorF56490(alpha: 0.2)
|
||||||
|
self.textLabel.textColor = .colorF564B6()
|
||||||
|
} else {
|
||||||
|
self.contentView.layer.borderColor = UIColor.clear.cgColor
|
||||||
|
self.contentView.backgroundColor = .colorFFFFFF(alpha: 0.2)
|
||||||
|
self.textLabel.textColor = .colorD2D2D2()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var textLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontLight(ofSize: 14)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.contentView.layer.cornerRadius = 7
|
||||||
|
self.contentView.layer.masksToBounds = true
|
||||||
|
self.contentView.layer.borderWidth = 1
|
||||||
|
|
||||||
|
contentView.addSubview(textLabel)
|
||||||
|
|
||||||
|
textLabel.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
93
ShortPlay/Class/Player/View/SPSpeedSelectedView.swift
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
//
|
||||||
|
// SPSpeedSelectedView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPSpeedSelectedView: UIView {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return CGSize(width: kSPScreenWidth, height: 54)
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedSpeed: ((_ model: SPSpeedModel) -> Void)?
|
||||||
|
|
||||||
|
var currentSpeed = SPSpeedModel.Speed.x1 {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var dataArr: [SPSpeedModel] = SPSpeedModel.getAllSpeed()
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.itemSize = .init(width: 70, height: 54)
|
||||||
|
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
|
||||||
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
SPSpeedSelectedCell.registerCell(collectionView: collectionView)
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPSpeedSelectedView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||||
|
extension SPSpeedSelectedView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let model = dataArr[indexPath.row]
|
||||||
|
let cell = SPSpeedSelectedCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
|
||||||
|
cell.model = model
|
||||||
|
cell.sp_isSelected = model.speed == currentSpeed
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
|
||||||
|
self.didSelectedSpeed?(dataArr[indexPath.row])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,18 +22,19 @@ class SPPlayerListViewModel: NSObject {
|
|||||||
self?.handlePlayFinish?()
|
self?.handlePlayFinish?()
|
||||||
}
|
}
|
||||||
_currentPlayer?.isCurrent = true
|
_currentPlayer?.isCurrent = true
|
||||||
|
_currentPlayer?.rate = speedModel.getRate()
|
||||||
}
|
}
|
||||||
get {
|
get {
|
||||||
return _currentPlayer
|
return _currentPlayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) var speed: SPSpeedModel.Speed = .x1
|
@objc dynamic private(set) lazy var speedModel = SPSpeedModel(speed: .x1)
|
||||||
|
|
||||||
///设置倍速播放
|
///设置倍速播放
|
||||||
func setSpeedPlay(speed: SPSpeedModel.Speed) {
|
func setSpeedPlay(speedModel: SPSpeedModel) {
|
||||||
self.speed = speed
|
self.speedModel = speedModel
|
||||||
currentPlayer?.rate = speed.getRate()
|
currentPlayer?.rate = speedModel.getRate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BIN
ShortPlay/Source/Assets.xcassets/icon/about_us_icon_01.imageset/About Us@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/about_us_icon_01.imageset/About Us@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.1 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/about_us_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "About Us@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "About Us@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
21
ShortPlay/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "nav_back@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/nav_back@2x.png
vendored
Normal file
After Width: | Height: | Size: 116 B |
22
ShortPlay/Source/Assets.xcassets/icon/help_center_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Help Center@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Help Center@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/help_center_icon_01.imageset/Help Center@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/help_center_icon_01.imageset/Help Center@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.0 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/language_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Language@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Language@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/language_icon_01.imageset/Language@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/language_icon_01.imageset/Language@3x.png
vendored
Normal file
After Width: | Height: | Size: 5.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/order_record_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Order Record@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Order Record@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/order_record_icon_01.imageset/Order Record@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/order_record_icon_01.imageset/Order Record@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/play_icon_02.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "play@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "play@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_02.imageset/play@2x.png
vendored
Normal file
After Width: | Height: | Size: 280 B |
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_02.imageset/play@3x.png
vendored
Normal file
After Width: | Height: | Size: 431 B |
22
ShortPlay/Source/Assets.xcassets/icon/privacy_policy_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Privacy Policy@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Privacy Policy@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/privacy_policy_icon_01.imageset/Privacy Policy@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/privacy_policy_icon_01.imageset/Privacy Policy@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.8 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/user_agreement_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "User Agreement@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "User Agreement@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/user_agreement_icon_01.imageset/User Agreement@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/user_agreement_icon_01.imageset/User Agreement@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.3 KiB |
@ -20,3 +20,13 @@
|
|||||||
"Episodes" = "Episodes";
|
"Episodes" = "Episodes";
|
||||||
"Save" = "Save";
|
"Save" = "Save";
|
||||||
"Added" = "Added";
|
"Added" = "Added";
|
||||||
|
"Series" = "Series";
|
||||||
|
"Order Record" = "Order Record";
|
||||||
|
"Language" = "Language";
|
||||||
|
"Privacy Policy" = "Privacy Policy";
|
||||||
|
"User Agreement" = "User Agreement";
|
||||||
|
"Help Center" = "Help Center";
|
||||||
|
"About Us" = "About Us";
|
||||||
|
|
||||||
|
///视频详情标题
|
||||||
|
"kPlayerDetailTitleString" = "Episode %@ / %@";
|
||||||
|
32
ShortPlay/Thirdparty/JXButton/JXButton.swift
vendored
@ -26,8 +26,36 @@ class JXButton: UIButton {
|
|||||||
private var titleRect: CGRect = .zero
|
private var titleRect: CGRect = .zero
|
||||||
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override class var layerClass: AnyClass {
|
||||||
super.layoutSubviews()
|
return CAGradientLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
var gradientLayer: CAGradientLayer {
|
||||||
|
return self.layer as! CAGradientLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
var locations: [NSNumber]? {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.locations = locations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors: [CGColor]? {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.colors = colors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startPoint: CGPoint = .zero {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.startPoint = startPoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endPoint: CGPoint = .zero {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.endPoint = endPoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
|