From 7a67b5824e737268cb121264b2bef95730488054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E8=A7=89=E6=96=B0?= Date: Wed, 16 Apr 2025 18:22:12 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=89=E9=9B=86=E5=8A=9F=E8=83=BD=E5=BC=80?= =?UTF-8?q?=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ShortPlay/AppDelegate/AppDelegate.swift | 1 - ShortPlay/Base/Extension/String+SPAdd.swift | 8 + ShortPlay/Base/Extension/UIColor+SPAdd.swift | 2 + .../Base/Networking/API/SPVideoAPI.swift | 33 +++ .../Base/Networking/Base/CryptorService.swift | 101 ++++++++ .../Base/Networking/Base/SPNetwork.swift | 15 +- .../Base/Networking/Base/SPURLPath.swift | 12 + .../Controller/SPForYouViewController.swift | 2 +- .../Class/Home/View/SPHomeHeaderView.swift | 2 +- ShortPlay/Class/Home/View/SPHomeHotView.swift | 2 +- .../Class/Home/View/SPHomeTrendingView.swift | 2 +- ...ift => SPPlayerDetailViewController.swift} | 45 +++- .../SPPlayerListViewController.swift | 16 ++ .../Class/Player/View/SPEpisodeCell.swift | 70 ++++++ .../Class/Player/View/SPEpisodeView.swift | 153 ++++++++++++ .../Player/View/SPPlayerControlView.swift | 80 ++++++- .../Player/View/SPPlayerDetailCell.swift | 26 +++ .../View/SPPlayerDetailControlView.swift | 44 ++++ .../Class/Player/View/SPPlayerListCell.swift | 31 ++- .../Player/View/SPTVPlayerListCell.swift | 12 - .../ViewModel/SPPlayerListViewModel.swift | 3 +- .../collect_icon_01.imageset/Contents.json | 22 ++ .../icon/collect_icon_01.imageset/收藏@2x.png | Bin 0 -> 1358 bytes .../icon/collect_icon_01.imageset/收藏@3x.png | Bin 0 -> 2343 bytes .../Contents.json | 22 ++ .../收藏@2x.png | Bin 0 -> 1829 bytes .../收藏@3x.png | Bin 0 -> 3353 bytes .../episodes_icon_01.imageset/Contents.json | 22 ++ .../episodes_icon_01.imageset/Episodes@2x.png | Bin 0 -> 1799 bytes .../episodes_icon_01.imageset/Episodes@3x.png | Bin 0 -> 3408 bytes .../icon/play_icon_01.imageset/Contents.json | 22 ++ .../icon/play_icon_01.imageset/play1@2x.png | Bin 0 -> 1288 bytes .../icon/play_icon_01.imageset/play1@3x.png | Bin 0 -> 2575 bytes ShortPlay/Source/ShortPlay-Bridging-Header.h | 1 + ShortPlay/Source/en.lproj/Localizable.strings | 3 + ShortPlay/Thirdparty/JXButton/JXButton.swift | 218 ++++++++++++++++++ 36 files changed, 926 insertions(+), 44 deletions(-) create mode 100644 ShortPlay/Base/Networking/Base/CryptorService.swift rename ShortPlay/Class/Player/Controller/{SPTVPlayerListViewController.swift => SPPlayerDetailViewController.swift} (62%) create mode 100644 ShortPlay/Class/Player/View/SPEpisodeCell.swift create mode 100644 ShortPlay/Class/Player/View/SPEpisodeView.swift create mode 100644 ShortPlay/Class/Player/View/SPPlayerDetailCell.swift create mode 100644 ShortPlay/Class/Player/View/SPPlayerDetailControlView.swift delete mode 100644 ShortPlay/Class/Player/View/SPTVPlayerListCell.swift create mode 100644 ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/Contents.json create mode 100644 ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@2x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@3x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Contents.json create mode 100644 ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@2x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@3x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Contents.json create mode 100644 ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@2x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@3x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/Contents.json create mode 100644 ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/play1@2x.png create mode 100644 ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/play1@3x.png create mode 100644 ShortPlay/Thirdparty/JXButton/JXButton.swift diff --git a/ShortPlay/AppDelegate/AppDelegate.swift b/ShortPlay/AppDelegate/AppDelegate.swift index 7a0747e..c8d5fa8 100644 --- a/ShortPlay/AppDelegate/AppDelegate.swift +++ b/ShortPlay/AppDelegate/AppDelegate.swift @@ -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) diff --git a/ShortPlay/Base/Extension/String+SPAdd.swift b/ShortPlay/Base/Extension/String+SPAdd.swift index 3741755..c95d4c4 100644 --- a/ShortPlay/Base/Extension/String+SPAdd.swift +++ b/ShortPlay/Base/Extension/String+SPAdd.swift @@ -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) + } +} diff --git a/ShortPlay/Base/Extension/UIColor+SPAdd.swift b/ShortPlay/Base/Extension/UIColor+SPAdd.swift index 8b47eca..9af98a0 100644 --- a/ShortPlay/Base/Extension/UIColor+SPAdd.swift +++ b/ShortPlay/Base/Extension/UIColor+SPAdd.swift @@ -63,5 +63,7 @@ extension UIColor { static func colorF56490(alpha: CGFloat = 1) -> UIColor { return color(hex: 0xF56490, alpha: alpha) } + + } diff --git a/ShortPlay/Base/Networking/API/SPVideoAPI.swift b/ShortPlay/Base/Networking/API/SPVideoAPI.swift index b28d529..862bb85 100644 --- a/ShortPlay/Base/Networking/API/SPVideoAPI.swift +++ b/ShortPlay/Base/Networking/API/SPVideoAPI.swift @@ -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) 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") + } diff --git a/ShortPlay/Base/Networking/Base/CryptorService.swift b/ShortPlay/Base/Networking/Base/CryptorService.swift new file mode 100644 index 0000000..9dad416 --- /dev/null +++ b/ShortPlay/Base/Networking/Base/CryptorService.swift @@ -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.. 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 { + 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 + } + } +} diff --git a/ShortPlay/Base/Networking/Base/SPNetwork.swift b/ShortPlay/Base/Networking/Base/SPNetwork.swift index ec514c5..eac6053 100644 --- a/ShortPlay/Base/Networking/Base/SPNetwork.swift +++ b/ShortPlay/Base/Networking/Base/SPNetwork.swift @@ -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? let time = Date().timeIntervalSince1970 - if let decrypted = SPCryptService.decrypt(data) { - spLog(message: decrypted) - response = SPNetworkResponse.deserialize(from: decrypted) - response?.rawData = decrypted - } + let decrypted = CryptorService.decrypt(data: data) + spLog(message: decrypted) + response = SPNetworkResponse.deserialize(from: decrypted) + response?.rawData = decrypted + spLog(message: Date().timeIntervalSince1970 - time) if let response = response { diff --git a/ShortPlay/Base/Networking/Base/SPURLPath.swift b/ShortPlay/Base/Networking/Base/SPURLPath.swift index e88ad32..d83137a 100644 --- a/ShortPlay/Base/Networking/Base/SPURLPath.swift +++ b/ShortPlay/Base/Networking/Base/SPURLPath.swift @@ -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" diff --git a/ShortPlay/Class/ForYou/Controller/SPForYouViewController.swift b/ShortPlay/Class/ForYou/Controller/SPForYouViewController.swift index ee4bee3..f352ee2 100644 --- a/ShortPlay/Class/ForYou/Controller/SPForYouViewController.swift +++ b/ShortPlay/Class/ForYou/Controller/SPForYouViewController.swift @@ -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 } } diff --git a/ShortPlay/Class/Home/View/SPHomeHeaderView.swift b/ShortPlay/Class/Home/View/SPHomeHeaderView.swift index 28c2d0e..0721233 100644 --- a/ShortPlay/Class/Home/View/SPHomeHeaderView.swift +++ b/ShortPlay/Class/Home/View/SPHomeHeaderView.swift @@ -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) } diff --git a/ShortPlay/Class/Home/View/SPHomeHotView.swift b/ShortPlay/Class/Home/View/SPHomeHotView.swift index 41203db..dbe622d 100644 --- a/ShortPlay/Class/Home/View/SPHomeHotView.swift +++ b/ShortPlay/Class/Home/View/SPHomeHotView.swift @@ -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) } diff --git a/ShortPlay/Class/Home/View/SPHomeTrendingView.swift b/ShortPlay/Class/Home/View/SPHomeTrendingView.swift index 1fd9a23..60fad83 100644 --- a/ShortPlay/Class/Home/View/SPHomeTrendingView.swift +++ b/ShortPlay/Class/Home/View/SPHomeTrendingView.swift @@ -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) } diff --git a/ShortPlay/Class/Player/Controller/SPTVPlayerListViewController.swift b/ShortPlay/Class/Player/Controller/SPPlayerDetailViewController.swift similarity index 62% rename from ShortPlay/Class/Player/Controller/SPTVPlayerListViewController.swift rename to ShortPlay/Class/Player/Controller/SPPlayerDetailViewController.swift index 9eae165..ad12a5d 100644 --- a/ShortPlay/Class/Player/Controller/SPTVPlayerListViewController.swift +++ b/ShortPlay/Class/Player/Controller/SPPlayerDetailViewController.swift @@ -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 } diff --git a/ShortPlay/Class/Player/Controller/SPPlayerListViewController.swift b/ShortPlay/Class/Player/Controller/SPPlayerListViewController.swift index 81d437f..54d6dff 100644 --- a/ShortPlay/Class/Player/Controller/SPPlayerListViewController.swift +++ b/ShortPlay/Class/Player/Controller/SPPlayerListViewController.swift @@ -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) + } } diff --git a/ShortPlay/Class/Player/View/SPEpisodeCell.swift b/ShortPlay/Class/Player/View/SPEpisodeCell.swift new file mode 100644 index 0000000..dfb8f7b --- /dev/null +++ b/ShortPlay/Class/Player/View/SPEpisodeCell.swift @@ -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() + } + } + + +} diff --git a/ShortPlay/Class/Player/View/SPEpisodeView.swift b/ShortPlay/Class/Player/View/SPEpisodeView.swift new file mode 100644 index 0000000..8a8a700 --- /dev/null +++ b/ShortPlay/Class/Player/View/SPEpisodeView.swift @@ -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) { + + } + } +} diff --git a/ShortPlay/Class/Player/View/SPPlayerControlView.swift b/ShortPlay/Class/Player/View/SPPlayerControlView.swift index 4403040..28c2158 100644 --- a/ShortPlay/Class/Player/View/SPPlayerControlView.swift +++ b/ShortPlay/Class/Player/View/SPPlayerControlView.swift @@ -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 + } } diff --git a/ShortPlay/Class/Player/View/SPPlayerDetailCell.swift b/ShortPlay/Class/Player/View/SPPlayerDetailCell.swift new file mode 100644 index 0000000..bfbec5e --- /dev/null +++ b/ShortPlay/Class/Player/View/SPPlayerDetailCell.swift @@ -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") + } + + +} diff --git a/ShortPlay/Class/Player/View/SPPlayerDetailControlView.swift b/ShortPlay/Class/Player/View/SPPlayerDetailControlView.swift new file mode 100644 index 0000000..65ba386 --- /dev/null +++ b/ShortPlay/Class/Player/View/SPPlayerDetailControlView.swift @@ -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?() + } +} diff --git a/ShortPlay/Class/Player/View/SPPlayerListCell.swift b/ShortPlay/Class/Player/View/SPPlayerListCell.swift index ea6b46a..1d543c1 100644 --- a/ShortPlay/Class/Player/View/SPPlayerListCell.swift +++ b/ShortPlay/Class/Player/View/SPPlayerListCell.swift @@ -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 ?? "") } } diff --git a/ShortPlay/Class/Player/View/SPTVPlayerListCell.swift b/ShortPlay/Class/Player/View/SPTVPlayerListCell.swift deleted file mode 100644 index 8084c20..0000000 --- a/ShortPlay/Class/Player/View/SPTVPlayerListCell.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SPTVPlayerListCell.swift -// ShortPlay -// -// Created by 曾觉新 on 2025/4/10. -// - -import UIKit - -class SPTVPlayerListCell: SPPlayerListCell { - -} diff --git a/ShortPlay/Class/Player/ViewModel/SPPlayerListViewModel.swift b/ShortPlay/Class/Player/ViewModel/SPPlayerListViewModel.swift index a2812c8..b6e4c36 100644 --- a/ShortPlay/Class/Player/ViewModel/SPPlayerListViewModel.swift +++ b/ShortPlay/Class/Player/ViewModel/SPPlayerListViewModel.swift @@ -41,6 +41,7 @@ class SPPlayerListViewModel: NSObject { var handlePauseOrPlay: (() -> Void)? ///播放完成 var handlePlayFinish: (() -> Void)? - + ///选集 + var handleEpisode: (() -> Void)? } diff --git a/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/Contents.json b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/Contents.json new file mode 100644 index 0000000..2fcc7b2 --- /dev/null +++ b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/Contents.json @@ -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 + } +} diff --git a/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@2x.png b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c64bf6e4b7ed8703538b4ecdd87726c69ff01e GIT binary patch literal 1358 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 literal 0 HcmV?d00001 diff --git a/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Contents.json b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..2fcc7b2 --- /dev/null +++ b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Contents.json @@ -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 + } +} diff --git a/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@2x.png b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f1e659fb33d52acf3d4a9850ef36f1b3d6315f35 GIT binary patch literal 1829 zcmV+=2io|FP)Px*-$_J4RCr$Pn|o}P3dqwf#;t$Z{Oeb^IXnztKcjza+c?ha{$h@fU}%{V_l$i!OHZ6)WqcWmbT;P zZ&=n#}=)c81ITJB-C{RTp@xS0twv#3D7~} zBLQ|p(_Tw%ZP_jxmUr3-*Z>-zoHEbw0br^Smr^jM#|F^}k^)QK>?KzTmQoZDI>5q)@Ej>Jx?up{3CebCo5#UY$i8lNw5U;tw z70GoE?6In63BbwXHQ8reSPh`is^P)p2y_T##d&StKGDBLd@=t?ciLF@3Q#Q_jt{d| zMP8CFE=^fm(HzQFNC0i6RfQz#0i=a8X5;(Y@wzHZuCMsQ&}l8Myijw=CIY!OY=jV% zBOn&0)p@rLB{(F2wi5p`1@-uUdkX*8W1M>88xhf~dlCia=zF-i7 z0%$F*eUQYTovV>SED27M&;&?|q#vb}C-WJPe=r||0yt7qKTiW+j!4Bj)qw2T(Unqj z#?_T`6fV;9|(nO%3&Q4>JZqWUB)A?uV~Bhf71naHF|o}L@%8T1d8Z97WB>=&_K zL~!hanmHMLeg&F5uQviRedK!K=#37V$es&ouAklyz?)AJSQz3bETtiureAf_RRv?aP-S=p=d%t61;!sioD z!yhU9T`U6-p!(vPpPJ2oAOLT}`J~oFFgZpc1YFcFy~sDS=Ky8E()XjX#y=4FeQW~I zR$f*!?cv_J4FIs$vo}6P*a+~d7=y6?aG+yG!Pn*X1dBi9{@;7v14RVZ0nnlhg02*Qk?YGfDlljX3Bb@i=sj3UNR8d&^ic#x z5{q+dCL4#kLl#Q_3=0RmAN-oYUqTuDethSza;qn;vJ#7B0EV&sSLct5Tr&@+{^>S!e72>7q8zQkk4mu&z9WuLd{d4P9+zqXIDeslG+ zcRh+nRYU<8#&_iJh)etN#FqiN&5pAO%5GKqk=w5JUEO8ZhwT>7?O@COA197Kle7ik z&+R&ORG!=&aBH*kR%M5^rx`W?b39nsJf`csGuuI!+e@QR_MbS_m0z$nr!`#P!vbJ9 zeYgBjdO#BX335ufL4>ErPbi%;r&Z>Jya_gx{b2(zjPF^}ltsGV65!(SlCiDT%r4|j zugz+*t=Uh(5denGf0Z=nFhSb|VnQSjSOGbz(KYLu>WqWKP2SLV41i&*v8-hhH0%VJ z=|pe?YzR6)xqe1fW}{Q~9R*-QC~y4WT=8~ zMDdYXRS7|Fc!!tj;94UFz=W`fNk~@r0-P@*c_iGuPNNTLgkM2hT~H|z`Jbb~5R52* z-T`d6pNZ}+{LuVc5?TxXDq$~ZhSMTO4Zv`Mc2f5o04Hfiqpfo-AR24U$(a8Gsz=Li TsL{NB00000NkvXXu0mjfyOw-i literal 0 HcmV?d00001 diff --git a/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@3x.png b/ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..74c35e77898d371e99ae23ce68d353870e35ec74 GIT binary patch literal 3353 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 literal 0 HcmV?d00001 diff --git a/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Contents.json b/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Contents.json new file mode 100644 index 0000000..292c6c2 --- /dev/null +++ b/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Contents.json @@ -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 + } +} diff --git a/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@2x.png b/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..86c068c638bc0e82582ed6b31d6b2e3cce3ade4d GIT binary patch literal 1799 zcmV+i2l)7jP)Px*!AV3xRCr$PTUV%FMG*eR-g^TT3n&&uK@=-07A#m|@4Y^$U_n9fQ3;CRlP!s9 znrMuPCSo*%D7FWEup(+A#uAMs_U`BZ`Ok^xo}B;e?z!jO+%_-w;qKX;{dQ(|zM1(A zBV25R?;oQ8j9S15M_|+iR)qnO+#0~e04@ZuI)F(4#+%va1Fg@1S^&wt0bCB?L;!0R zHv1QVIRG9svo{6`h}HohsS`dHz!jYh?iaZ+0q_8Txn?F{T9eiQAh{8Ma{ycdVEdXo zkV}jQFvZNiuQ^BMXN?0uaz_A{0XPf5`VA@l3xLNuKt5zhzmEd2 zT9ZoV=K2S~0>39-Xkv|i0YGvs0AqaPdo-a?!W{1l2mmw8?DvH4`V6fF0Ld-=3DYyS zMcu_4`tDnQMjtk_FH`4C0)XV6e%dDkSUYvGmdF1A;3;>HH)Gcd0zgv!e6&Af2Q+za z#41^1+-v@fK5J&WH+o7>07&WvKO4ZM0CcktMOgx19Dqm6?8mYVlmbBVS^(Do*l;NB zxjshT?grwsBivU~z zKzv{@$zsuE0MpD&^f^kB0LTF$7I=zV#Xz&b;>KQ;nFE+-W{lc()iann0m*|Lh&J;8 zMYZJLvlZ`~*;}bPSH1#uC(C7m&IGVQ-FX7vEqCfW!OWI6u|_X0AU_ua)P*w1%w%R! z(oin2xYnYafBzb#NcZwqmg>rmrEIGg!OZRnK7%B$2XI4Spu*$(#J9~%<`)#F(OEWl z|KJDZ173DBpBMW*Nu}Gf`>9XZ+aY&%nc3ZyE8lQ^NI z9#EVnOKj-<7VuF*ax;IOl!fl~LM<^fNZ{&`wd9}d>Vk0+#+Tw z?Oo*RRr8TN+&8Yjv2u%xiH!0+p}F%E!6aqewMq(yRjPQ$9Wby z;~}yxQZO}y-NIIQFO1_^;71?q@^SA)dV zlm-cs3U~E-98)OOM+HLK5#R^#I7SZQt#1KoiKf`9*Qk8 zvqSU$E8_w(d)>%YqSh7F+)_`biWI3AXs*W%(M?h$Rm4$%Bod^6sY;fb*}fqF#F@_s z9&LAka&M2iZz?AjB{@Da_ghk{=B3w5Ii=#P&)!C&BOr2gSRbu56v3kE{{=o9!V7omwDC5yM>WNG-6Rvg`9N|6UCb`XG^ zMdUk06r;zAk|#1h`JX6rP-=3HtbCh_)TbwH6`tom8f+`!JV)SSPfP#C-d=-YuoSW(x&F&Gk%8CUx zCXEA-a}RNPu|t`6T~eg#&jfFlRzqat(i#9cAViA9^%bhdk|Kq|GILdPhGx6`O3vH; z-N12v*_HinAZut)NF2=t`fC!cTR@qD{>u^F69Pp2y-zA=YO3Iw0?H}@AVtBp$BqKf pHo}8_{!t4U>?hQ=ek0xj{sq~xC0|wX29E#$002ovPDHLkV1gSdK&Su! literal 0 HcmV?d00001 diff --git a/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@3x.png b/ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c97f7e84398a696b003ac8c35fdcbd344180a90d GIT binary patch literal 3408 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 lYBYcrj-R literal 0 HcmV?d00001 diff --git a/ShortPlay/Source/ShortPlay-Bridging-Header.h b/ShortPlay/Source/ShortPlay-Bridging-Header.h index a8edb74..e70f190 100644 --- a/ShortPlay/Source/ShortPlay-Bridging-Header.h +++ b/ShortPlay/Source/ShortPlay-Bridging-Header.h @@ -11,3 +11,4 @@ #import #import "NSUserDefaults+JXAdd.h" #import +#import diff --git a/ShortPlay/Source/en.lproj/Localizable.strings b/ShortPlay/Source/en.lproj/Localizable.strings index 7a783b8..e31b88f 100644 --- a/ShortPlay/Source/en.lproj/Localizable.strings +++ b/ShortPlay/Source/en.lproj/Localizable.strings @@ -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"; diff --git a/ShortPlay/Thirdparty/JXButton/JXButton.swift b/ShortPlay/Thirdparty/JXButton/JXButton.swift new file mode 100644 index 0000000..dbf2ae8 --- /dev/null +++ b/ShortPlay/Thirdparty/JXButton/JXButton.swift @@ -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 + } + } + +}