首页更多页面,W2A统计功能,Bug处理

This commit is contained in:
zjx 2025-06-09 18:27:24 +08:00
parent eaa1d76bbe
commit a30c319c05
36 changed files with 687 additions and 56 deletions

View File

@ -217,6 +217,8 @@
BFF5B26E2DF297680044227A /* VPOpenAppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B26D2DF297680044227A /* VPOpenAppModel.swift */; };
BFF5B2752DF2C3750044227A /* VPDetailRecommandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2742DF2C3750044227A /* VPDetailRecommandView.swift */; };
BFF5B2772DF2CA4B0044227A /* VPDetailRecommandBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2762DF2CA4B0044227A /* VPDetailRecommandBannerCell.swift */; };
BFF5B2792DF679720044227A /* VPMoreVideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2782DF679720044227A /* VPMoreVideoViewController.swift */; };
BFF5B4AF2DF6B6630044227A /* VPMutualCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B4AE2DF6B6630044227A /* VPMutualCollectionView.swift */; };
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
/* End PBXBuildFile section */
@ -439,6 +441,8 @@
BFF5B26D2DF297680044227A /* VPOpenAppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPOpenAppModel.swift; sourceTree = "<group>"; };
BFF5B2742DF2C3750044227A /* VPDetailRecommandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDetailRecommandView.swift; sourceTree = "<group>"; };
BFF5B2762DF2CA4B0044227A /* VPDetailRecommandBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDetailRecommandBannerCell.swift; sourceTree = "<group>"; };
BFF5B2782DF679720044227A /* VPMoreVideoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPMoreVideoViewController.swift; sourceTree = "<group>"; };
BFF5B4AE2DF6B6630044227A /* VPMutualCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPMutualCollectionView.swift; sourceTree = "<group>"; };
E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -676,6 +680,7 @@
1B056E562DDACC6B007EE38D /* VPHomePageViewController.swift */,
BF0FA7382DDECF8900C9E5F2 /* VPHomeListViewController.swift */,
BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */,
BFF5B2782DF679720044227A /* VPMoreVideoViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
@ -909,6 +914,7 @@
BFF5AFE12DEED2960044227A /* VPPlayerCoinBuyView.swift */,
BFF5B2742DF2C3750044227A /* VPDetailRecommandView.swift */,
BFF5B2762DF2CA4B0044227A /* VPDetailRecommandBannerCell.swift */,
BFF5B4AE2DF6B6630044227A /* VPMutualCollectionView.swift */,
);
path = View;
sourceTree = "<group>";
@ -1433,6 +1439,7 @@
BFF5AFD42DE9A5FB0044227A /* VPVIPRecordCell.swift in Sources */,
BFF5AFD62DE9A8D70044227A /* VPWalletHeaderView.swift in Sources */,
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */,
BFF5B2792DF679720044227A /* VPMoreVideoViewController.swift in Sources */,
BF5E75B12DE4656600DE9DFE /* VPCampaignWebViewController.swift in Sources */,
BF5E75BC2DE546AD00DE9DFE /* VPAboutUsCell.swift in Sources */,
BFF5AFD02DE9A0370044227A /* VPCoinRecordCell.swift in Sources */,
@ -1522,6 +1529,7 @@
BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */,
BFF5B2612DF16B430044227A /* JXIAPManager.swift in Sources */,
BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */,
BFF5B4AF2DF6B6630044227A /* VPMutualCollectionView.swift in Sources */,
BF0FA7612DDFFE7100C9E5F2 /* VPVideoDetailModel.swift in Sources */,
BFF5AFD22DE9A58A0044227A /* VPVIPRecordViewController.swift in Sources */,
BFF5AFDA2DEE90350044227A /* VPVideoLockView.swift in Sources */,

View File

@ -16,7 +16,7 @@ extension SceneDelegate {
}
//facebook
var result = ApplicationDelegate.shared.application(UIApplication.shared, open: url, sourceApplication: nil, annotation: [UIApplication.OpenURLOptionsKey.annotation])
let result = ApplicationDelegate.shared.application(UIApplication.shared, open: url, sourceApplication: nil, annotation: [UIApplication.OpenURLOptionsKey.annotation])
if !result {
vp_handleOpenAppMessage(webpageURL: url)
@ -33,13 +33,17 @@ extension SceneDelegate {
extension SceneDelegate {
static var hasOpenMessage = false
///
static var isNeedRetry = false
func vp_handleOpenAppMessage(webpageURL: URL?) {
guard VPNetworkReachabilityManager.manager.isReachable == true else { return }
guard VPNetworkReachabilityManager.manager.isReachable == true else {
Self.isNeedRetry = true
return
}
Self.isNeedRetry = false
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self._handleOpenAppMessage(webpageURL: webpageURL)
}
@ -50,7 +54,7 @@ extension SceneDelegate {
Self.hasOpenMessage = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
Self.hasOpenMessage = false
}
@ -58,7 +62,8 @@ extension SceneDelegate {
var statUrlStr: String? = webpageURL?.absoluteString
var data: [String : Any]? = webpageURL?.query?.vp_urlQuryToDictionary()
if let pasteStr = UIPasteboard.general.string {
if let pasteStr = UIPasteboard.general.string, pasteStr.contains("veloriaapp") {
UIPasteboard.general.string = nil
let tempArr = pasteStr.components(separatedBy: "?")
let query = tempArr.last
@ -70,6 +75,10 @@ extension SceneDelegate {
}
}
if let urlStr = statUrlStr {//
VPStatAPI.requestStatW2a(data: urlStr)
}
@ -86,4 +95,10 @@ extension SceneDelegate {
}
///
func vp_retryHandleOpenAppMessage() {
guard Self.isNeedRetry else { return }
vp_handleOpenAppMessage(webpageURL: nil)
}
}

View File

@ -30,25 +30,55 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
VPStatAPI.requestEnterApp()
vp_handleOpenAppMessage(webpageURL: nil)
// vpLog(message: "++++++++++++++sceneDidBecomeActive")
enterForeground()
}
func sceneWillResignActive(_ scene: UIScene) {
VPStatAPI.requestLeaveApp()
// vpLog(message: "++++++++++++++sceneWillResignActive")
enterBackground()
}
func sceneWillEnterForeground(_ scene: UIScene) {
// vpLog(message: "++++++++++++++sceneWillEnterForeground")
// enterForeground()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.vp_handleOpenAppMessage(webpageURL: nil)
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
// vpLog(message: "++++++++++++++sceneDidEnterBackground")
// enterBackground()
}
// private var isEnterForeground = false
private func enterForeground() {
// if !isEnterForeground {
// vpLog(message: "++++++++++++++enterForeground")
// }
// isEnterForeground = true
handleOnLine()
VPStatAPI.requestEnterApp()
}
private func enterBackground() {
// if isEnterForeground {
// }
// vpLog(message: "++++++++++++++enterBackground")
// isEnterForeground = false
VPStatAPI.requestLeaveApp()
}
}
extension SceneDelegate {
@ -59,8 +89,11 @@ extension SceneDelegate {
@objc private func reachabilityDidChangeNotification() {
vp_handleOpenAppMessage(webpageURL: nil)
vp_retryHandleOpenAppMessage()
if VPNetworkReachabilityManager.manager.isReachable == true {
handleOnLine()
}
}
}

View File

@ -15,4 +15,10 @@ extension Date {
return dateComponents.day ?? 0
}
func formatString(dateFormat: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = dateFormat
return formatter.string(from: self)
}
}

View File

@ -51,3 +51,24 @@ extension String {
}
}
extension String {
func vp_range(of searchString: String) -> [NSRange] {
do {
let newSearch = searchString.lowercased()
let newText = self.lowercased()
let regex = try NSRegularExpression(pattern: newSearch)
let matches = regex.matches(in: newText, range: NSRange(self.startIndex..., in: newText))
let ranges = matches.map { $0.range }
return ranges
} catch {
return []
}
}
}

View File

@ -53,4 +53,14 @@ class VPHomeAPI: NSObject {
}
}
static func requestCategories(completer: ((_ list: [VPCategoryModel]?) -> Void)?) {
var param = VPNetworkParameters(path: "/getCategories")
param.method = .get
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPCategoryModel>>) in
completer?(response.data?.list)
}
}
}

View File

@ -80,8 +80,15 @@ class VPNetwork: NSObject {
}
completion?(res)
} else {
if code == 402, parameters.isToast {
VPToast.show(text: "kNetworkToast02".localized)
}
///token
self.requestToken(completer: nil)
self.requestToken { token in
if token != nil {
VPLoginManager.manager.updateUserInfo(completer: nil)
}
}
///
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
@ -137,7 +144,7 @@ class VPNetwork: NSObject {
var res = VPNetworkResponse<T>()
res.code = -1
if parameters.isToast {
VPToast.show(text: "network_toast_01".localized)
VPToast.show(text: "kNetworkToast01".localized)
}
completion?(res)
break

View File

@ -34,7 +34,11 @@ extension VPWebViewController {
self.navigationController?.pushViewController(vc, animated: true)
case VPWebMessageOpenFeedbackDetail:
guard let body = body as? [String : Any] else { return }
guard let id = body["id"] as? Int else { return }
let vc = VPCampaignWebViewController()
vc.id = "\(id)"
vc.bgImageView.isHidden = true
vc.urlStr = kVPFeedBackDetailWebUrl
self.navigationController?.pushViewController(vc, animated: true)

View File

@ -22,7 +22,8 @@ class VPExploreViewController: VPVideoPlayerViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loginStateDidChangeNotification), name: VPLoginManager.loginStateDidChangeNotification, object: nil)
self.collectionView.vp_onRefrsh = true
self.collectionView.vp_refreshDelegate = self
self.delegate = self
self.dataSource = self
@ -75,22 +76,37 @@ extension VPExploreViewController: VPPlayerListViewControllerDataSource {
}
//MARK: -------------- VPMutualCollectionViewDelegate --------------
extension VPExploreViewController: VPMutualCollectionViewDelegate {
func vp_loadNewData(collectionView: VPMutualCollectionView) {
collectionView.endRefresh()
requestDataArr(page: 1) { [weak self] in
guard let self = self else { return }
self.collectionView.endRefresh()
}
}
}
extension VPExploreViewController {
private func requestDataArr(page: Int) {
private func requestDataArr(page: Int, completer: (() -> Void)? = nil) {
VPVideoAPI.requestRecommandsVideo(page: page) { [weak self] listModel in
guard let self = self else { return }
if let listModel = listModel, let list = listModel.list {
if page == 1 {
self.setDataArr(dataArr: list) { [weak self] in
self?.play()
self?.scrollToItem(indexPath: IndexPath(row: 0, section: 0), animated: false)
// self?.play()
}
} else {
self.addDataArr(dataArr: list)
}
self.pagination = listModel.pagination
}
completer?()
}
}

View File

@ -18,7 +18,7 @@ class VPHomePageViewController: VPViewController {
param.wViewController = { [weak self] index in
let categoryModel = self?.viewModel.categoryList[index]
let vc = VPHomeListViewController()
vc.categoryId = categoryModel?.category_id
vc.categoryId = categoryModel?.id
return vc
}
@ -128,6 +128,7 @@ class VPHomePageViewController: VPViewController {
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityDidChangeNotification), name: VPNetworkReachabilityManager.reachabilityDidChangeNotification, object: nil)
requestHomeData()
requestCategories()
setupPageView()
vp_setupUI()
@ -147,6 +148,7 @@ class VPHomePageViewController: VPViewController {
self.pageView.downSc?.vp_endHeaderRefreshing()
}
self.requestHomeData()
self.requestCategories()
}
@ -187,6 +189,7 @@ extension VPHomePageViewController {
if self.viewModel.oldModuleList == nil {
requestHomeData()
}
requestCategories()
}
}
@ -247,18 +250,25 @@ extension VPHomePageViewController {
guard let self = self else { return }
if let list = list {
self.viewModel.oldModuleList = list
if self.viewModel.needUpdateCategory {
self.viewModel.needUpdateCategory = false
self.pageParam.wTitleArr = self.viewModel.categoryTitleList
self.pageView.updateMenuData()
self.setupPageView()
}
self.searchButton.marqueeArr = self.viewModel.marqueeArr
self.pageView.downSc?.reloadData()
}
}
}
private func requestCategories() {
if self.viewModel.rawCategoryList.count > 0 { return }
VPHomeAPI.requestCategories { [weak self] list in
guard let self = self else { return }
if let list = list {
self.viewModel.rawCategoryList = list
self.pageParam.wTitleArr = self.viewModel.categoryTitleList
self.pageView.updateMenuData()
self.setupPageView()
}
}
}
}

View File

@ -0,0 +1,118 @@
//
// VPMoreVideoViewController.swift
// Veloria
//
// Created by on 2025/6/9.
//
import UIKit
class VPMoreVideoViewController: VPViewController {
var dataArr: [VPShortModel] = [] {
didSet {
self.collectionView.reloadData()
}
}
private lazy var titleLeftImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "title_icon_01"))
return imageView
}()
private lazy var titleRightImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "title_icon_02"))
return imageView
}()
private(set) lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 18)
label.textColor = .colorFFFFFF()
return label
}()
// VPSearchResultCell
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: UIScreen.width - 30, height: 108)
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
layout.minimumLineSpacing = 10
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.tabbarSafeBottomMargin + 10, right: 0)
collectionView.register(VPSearchResultCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
vp_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
setNavigationNormalStyle()
}
}
extension VPMoreVideoViewController {
private func vp_setupUI() {
view.addSubview(titleLeftImageView)
view.addSubview(titleRightImageView)
view.addSubview(titleLabel)
view.addSubview(collectionView)
titleLeftImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(UIScreen.navBarHeight + 10)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(titleLeftImageView.snp.right).offset(1)
make.centerY.equalTo(titleLeftImageView)
}
titleRightImageView.snp.makeConstraints { make in
make.centerY.equalTo(titleLeftImageView)
make.left.equalTo(titleLabel.snp.right).offset(1)
}
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(UIScreen.navBarHeight + 60)
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension VPMoreVideoViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPSearchResultCell
cell.model = dataArr[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataArr.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = dataArr[indexPath.row]
let vc = VPDetailPlayerViewController()
vc.shortPlayId = model.short_play_id
self.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -10,6 +10,6 @@ import SmartCodable
class VPCategoryModel: VPModel, SmartCodable {
var category_name: String?
var category_id: String?
var name: String?
var id: String?
}

View File

@ -16,7 +16,7 @@ class VPHomeModuleItem: VPModel, SmartCodable {
///
case cagetory_recommand = "home_cagetory_recommand"
case week_ranking = "week_ranking"
case category_navigation = "category_navigation"
// case category_navigation = "category_navigation"
///
case marquee = "marquee"
}
@ -33,11 +33,11 @@ class VPHomeModuleItem: VPModel, SmartCodable {
func didFinishMapping() {
if let data = data as? [[String : Any]] {
if module_key == .category_navigation {
self.categoryList = [VPCategoryModel].deserialize(from: data)
} else {
self.list = [VPShortModel].deserialize(from: data)
}
// if module_key == .category_navigation {
// self.categoryList = [VPCategoryModel].deserialize(from: data)
// } else {
// }
self.list = [VPShortModel].deserialize(from: data)
} else if let data = data as? [String : Any] {
var dataList: [[String : Any]]?
if let list = data["list"] as? [[String : Any]] {

View File

@ -38,6 +38,18 @@ class VPHomeRankingContentCell: VPHomeItemContentCell {
return label
}()
private lazy var moreButton: UIButton = {
var config = UIButton.Configuration.plain()
config.imagePadding = 1
config.imagePlacement = .trailing
config.contentInsets = .init(top: 5, leading: 0, bottom: 5, trailing: 0)
config.image = UIImage(named: "arrow_right_icon_03")
config.attributedTitle = AttributedString.createAttributedString(string: "More".localized, color: .colorFFFFFF(alpha: 0.5), font: .fontRegular(ofSize: 12))
let button = UIButton(configuration: config)
button.addTarget(self, action: #selector(handleMoreButton), for: .touchUpInside)
return button
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
@ -67,6 +79,13 @@ class VPHomeRankingContentCell: VPHomeItemContentCell {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleMoreButton() {
let vc = VPMoreVideoViewController()
vc.titleLabel.text = titleLabel.text
vc.dataArr = self.item?.list ?? []
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}
extension VPHomeRankingContentCell {
@ -75,6 +94,7 @@ extension VPHomeRankingContentCell {
containerView.addSubview(iconImageView)
containerView.addSubview(titleLabel)
containerView.addSubview(collectionView)
containerView.addSubview(moreButton)
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
@ -92,6 +112,10 @@ extension VPHomeRankingContentCell {
make.height.equalTo(collectionViewLayout.itemSize.height * 3 + collectionViewLayout.minimumInteritemSpacing * 2)
}
moreButton.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.right.equalToSuperview().offset(-13)
}
}
}

View File

@ -32,6 +32,18 @@ class VPHomeRecommandContentCell: VPHomeItemContentCell {
return label
}()
private lazy var moreButton: UIButton = {
var config = UIButton.Configuration.plain()
config.imagePadding = 1
config.imagePlacement = .trailing
config.contentInsets = .init(top: 5, leading: 0, bottom: 5, trailing: 0)
config.image = UIImage(named: "arrow_right_icon_03")
config.attributedTitle = AttributedString.createAttributedString(string: "More".localized, color: .colorFFFFFF(alpha: 0.5), font: .fontRegular(ofSize: 12))
let button = UIButton(configuration: config)
button.addTarget(self, action: #selector(handleMoreButton), for: .touchUpInside)
return button
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
@ -60,6 +72,13 @@ class VPHomeRecommandContentCell: VPHomeItemContentCell {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleMoreButton() {
let vc = VPMoreVideoViewController()
vc.titleLabel.text = titleLabel.text
vc.dataArr = self.item?.list ?? []
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}
extension VPHomeRecommandContentCell {
@ -67,6 +86,7 @@ extension VPHomeRecommandContentCell {
private func vp_setupUI() {
containerView.addSubview(titleLabel)
containerView.addSubview(collectionView)
containerView.addSubview(moreButton)
if Self.moduleKey == .v3_recommand {
@ -97,6 +117,11 @@ extension VPHomeRecommandContentCell {
make.top.equalToSuperview().offset(32)
make.height.equalTo(collectionViewLayout.itemSize.height)
}
moreButton.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.right.equalToSuperview().offset(-13)
}
}
}

View File

@ -9,18 +9,28 @@ import UIKit
class VPSearchResultCell: VPCollectionViewCell {
var searchText: String? {
didSet {
}
}
var searchText: String?
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
videoNameLabel.text = model?.name
desLabel.text = model?.vp_description
let name = model?.name ?? ""
let ranges = name.vp_range(of: searchText ?? "")
let nameString = NSMutableAttributedString(string: name)
ranges.forEach {
nameString.setColor(.colorBE0069(), range: $0)
}
videoNameLabel.attributedText = nameString
// videoNameLabel.text = model?.name
let watchCount = model?.watch_total ?? 0
if watchCount > 1000 {
let numStr = NSNumber(floatLiteral: CGFloat(watchCount) / 1000).toString(maximumFractionDigits: 1)

View File

@ -12,8 +12,13 @@ class VPSearchResultView: UIView {
var viewModel: VPSearchViewModel?
private lazy var dataArr: [VPShortModel] = []
///
private lazy var currentDataText = ""
private(set) lazy var searchText: String = ""
//MARK: UI
private lazy var titleLabel: UILabel = {
let label = UILabel()
@ -26,7 +31,7 @@ class VPSearchResultView: UIView {
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: UIScreen.width - 30, height: 108)
layout.sectionInset = .init(top: 0, left: 15, bottom: UIScreen.tabbarSafeBottomMargin + 10, right: 15)
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
layout.minimumLineSpacing = 10
return layout
}()
@ -35,6 +40,7 @@ class VPSearchResultView: UIView {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.tabbarSafeBottomMargin + 10, right: 0)
collectionView.register(VPSearchResultCell.self, forCellWithReuseIdentifier: "cell")
collectionView.vp_addNormalEmpty(image: UIImage(named: "empty_image_02"))
collectionView.keyboardDismissMode = .onDrag
@ -80,7 +86,7 @@ extension VPSearchResultView {
extension VPSearchResultView: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPSearchResultCell
cell.searchText = self.searchText
cell.searchText = self.currentDataText
cell.model = dataArr[indexPath.row]
return cell
}
@ -109,6 +115,7 @@ extension VPSearchResultView {
if let list = list {
self.dataArr = list
self.currentDataText = text
self.collectionView.reloadData()
}
}

View File

@ -26,6 +26,25 @@ class VPHomeViewModel: VPModel {
private(set) lazy var categoryList: [VPCategoryModel] = []
private(set) lazy var categoryTitleList: [String] = []
var rawCategoryList: [VPCategoryModel] = [] {
didSet {
let allCategory = VPCategoryModel()
allCategory.name = "All".localized
allCategory.id = "0"
categoryList.removeAll()
categoryTitleList.removeAll()
categoryList.append(allCategory)
categoryList += rawCategoryList
categoryList.forEach { model in
categoryTitleList.append(model.name ?? "")
}
}
}
///
private(set) lazy var marqueeArr: [VPShortModel] = []
@ -39,23 +58,25 @@ class VPHomeViewModel: VPModel {
if key == .banner {
newModuleList.insert($0, at: 0)
} else if key == .category_navigation { //
}
/* else if key == .category_navigation { //
if needUpdateCategory {
categoryList.removeAll()
categoryTitleList.removeAll()
let allCategory = VPCategoryModel()
allCategory.category_name = "All".localized
allCategory.category_id = "0"
allCategory.name = "All".localized
allCategory.id = "0"
categoryList.append(allCategory)
categoryList += ($0.categoryList ?? [])
categoryList.forEach { model in
categoryTitleList.append(model.category_name ?? "")
categoryTitleList.append(model.name ?? "")
}
}
} else if key == .marquee { //
}*/
else if key == .marquee { //
marqueeArr = $0.list ?? []
} else {
newModuleList.append($0)

View File

@ -115,6 +115,8 @@ extension VPMeViewController: UITableViewDelegate, UITableViewDataSource {
cell.userInfo = VPLoginManager.manager.userInfo
} else if let cell = cell as? VPMeCoinCell {
cell.userInfo = VPLoginManager.manager.userInfo
} else if let cell = cell as? VPMeVipCell {
cell.userInfo = VPLoginManager.manager.userInfo
}
return cell

View File

@ -9,6 +9,20 @@ import UIKit
class VPMeVipCell: VPTableViewCell {
var userInfo: VPUserInfo? {
didSet {
if userInfo?.is_vip == true {
let date = Date(timeIntervalSince1970: userInfo?.vip_end_time ?? 0)
subtitleLabel.text = String(format: "kVipTipText2".localized, date.formatString(dateFormat: "yyyy-MM-dd"))
titleLabel.text = "VIP Active".localized
} else {
subtitleLabel.text = "kVipTipText1".localized
titleLabel.text = "Join VIP".localized
}
}
}
private lazy var bgView: UIView = {
let view = VPGradientView()
view.colors = [UIColor.color06DEAD(alpha: 0.32).cgColor, UIColor.colorFFFFFF(alpha: 0.05).cgColor]
@ -36,7 +50,6 @@ class VPMeVipCell: VPTableViewCell {
let label = UILabel()
label.font = .fontBold(ofSize: 15)
label.textColor = .colorFFFFFF()
label.text = "Join VIP".localized
return label
}()
@ -44,7 +57,6 @@ class VPMeVipCell: VPTableViewCell {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .colorFFFFFF()
label.text = "kVipTipText1".localized
return label
}()

View File

@ -59,9 +59,12 @@ class VPCollectListViewController: VPViewController {
///
var didChangeAllSelected: (() -> Void)?
///
var cancelEditBlock: (() -> Void)?
private lazy var deleteGroup = DispatchGroup()
private lazy var dataArr: [VPShortModel] = []
private(set) lazy var dataArr: [VPShortModel] = []
private var page: Int = 1
///1
@ -287,6 +290,7 @@ extension VPCollectListViewController {
self.deleteGroup.notify(queue: .main) { [weak self] in
VPHUD.dismiss()
guard let self = self else { return }
self.cancelEditBlock?()
self.requestDataArr(page: 1, completer: nil)
}
}

View File

@ -17,6 +17,11 @@ class VPMyListViewController: VPViewController {
///0 1
private var editState: EditState = .normal {
didSet {
if collectVC.dataArr.count <= 0, editState != .normal {
editState = .normal
return
}
if editState == .normal {
pageView.upSc.mainView.isHidden = false
pageView.upSc.dataView.isScrollEnabled = true
@ -30,7 +35,13 @@ class VPMyListViewController: VPViewController {
}
}
private lazy var collectVC = VPCollectListViewController()
private lazy var collectVC: VPCollectListViewController = {
let vc = VPCollectListViewController()
vc.cancelEditBlock = { [weak self] in
self?.editState = .normal
}
return vc
}()
private lazy var viewControllers: [UIViewController] = {
let vc2 = VPWatchHistoryViewController()

View File

@ -65,8 +65,8 @@ class VPVideoPlayerViewController: VPViewController {
return layout
}()
private(set) lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
private(set) lazy var collectionView: VPMutualCollectionView = {
let collectionView = VPMutualCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
@ -74,7 +74,6 @@ class VPVideoPlayerViewController: VPViewController {
collectionView.showsHorizontalScrollIndicator = false
collectionView.bounces = false
collectionView.scrollsToTop = false
// PlayerCellClass.registerCell(collectionView: collectionView)
collectionView.register(PlayerCellClass.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()

View File

@ -0,0 +1,188 @@
//
// VPMutualCollectionView.swift
// Veloria
//
// Created by on 2025/6/9.
//
import UIKit
@objc protocol VPMutualCollectionViewDelegate: NSObjectProtocol {
@objc optional func vp_loadNewData(collectionView: VPMutualCollectionView)
}
class VPMutualCollectionView: VPCollectionView {
enum RefrshStatus {
///
case normal
///
case pulling
///
case refreshing
}
///
var refreshStatus = RefrshStatus.normal
weak var vp_refreshDelegate: VPMutualCollectionViewDelegate?
///
lazy var refreshPanGesture: UIPanGestureRecognizer = {
let pan = UIPanGestureRecognizer(target: self, action: #selector(handleRefreshPanGesture(sender:)))
pan.delegate = self
return pan
}()
///
lazy var refreshView: UIActivityIndicatorView = {
let view = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
view.isHidden = true
// view.tintColor = .colorFFFFFF()
view.color = .colorFFFFFF()
return view
}()
///
var vp_onRefrsh = false
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.addGestureRecognizer(refreshPanGesture)
addSubview(refreshView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
refreshView.centerX = self.width / 2
}
@objc func handleRefreshPanGesture(sender: UIPanGestureRecognizer) {
if !vp_onRefrsh {return}
if refreshStatus == .refreshing {return}
switch sender.state {
case .began:
refreshStatus = .normal
refreshView.isHidden = false
refreshView.top = -20
bringSubviewToFront(refreshView)
sender.setTranslation(.zero, in: self)
case .changed:
let point = sender.translation(in: self)
var top = refreshView.top + point.y
if top > UIScreen.tabbarSafeBottomMargin + 70 {
top = UIScreen.tabbarSafeBottomMargin + 70
}
if top >= UIScreen.tabbarSafeBottomMargin + 60 {
refreshStatus = .pulling
} else {
refreshStatus = .normal
}
refreshView.top = top
sender.setTranslation(.zero, in: self)
case .cancelled, .ended, .failed:
if refreshStatus == .pulling {
beginRefresh()
} else {
endRefresh()
}
default:
break
}
}
func beginRefresh() {
self.refreshStatus = .refreshing
self.refreshView.startAnimating()
UIView.animate(withDuration: 0.5) { [weak self] in
self?.refreshView.top = UIScreen.tabbarSafeBottomMargin + 60
} completion: { [weak self] (finish) in
guard let self = self else {return}
self.vp_refreshDelegate?.vp_loadNewData?(collectionView: self)
}
}
func endRefresh() {
self.refreshStatus = .normal
self.refreshView.stopAnimating()
UIView.animate(withDuration: 0.5) { [weak self] in
self?.refreshView.top = -20
} completion: { [weak self] (finish) in
if self?.refreshStatus == .none {
self?.refreshView.isHidden = true
}
}
}
}
extension VPMutualCollectionView {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// return panBack(gestureRecognizer: gestureRecognizer)
if gestureRecognizer == self.refreshPanGesture {
if !vp_onRefrsh { return false }
// if self.isScrollEnabled == false { return false }
let point = self.refreshPanGesture.translation(in: self)
let state = gestureRecognizer.state
if state == .began || state == .possible {
///
if point.y > 0 && self.contentOffset.y == 0 {
return true
}
}
return false
} else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
func panBack(gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.refreshPanGesture {
if !vp_onRefrsh { return false }
// if self.isScrollEnabled == false { return false }
let point = self.refreshPanGesture.translation(in: self)
let state = gestureRecognizer.state
if state == .began || state == .possible {
///
if point.y > 0 && self.contentOffset.y == 0 {
return true
}
}
return false
} else if (gestureRecognizer == self.panGestureRecognizer) {
let point = self.panGestureRecognizer.translation(in: self)
let state = gestureRecognizer.state
if state == .began || state == .possible {
///
if point.y > 0 && self.contentOffset.y == 0 {
return false
}
}
return true
} else {
return true
}
}
}

View File

@ -63,12 +63,15 @@ class VPLoginManager: NSObject {
///退
func logout(completer: ((_ isFinish: Bool) -> Void)?) {
VPStatAPI.requestLeaveApp()
VPUserAPI.requestLogout { [weak self] token in
guard let self = self else { return }
if let token = token {
self.setLoginToken(token: token)
self.userInfo?.is_tourist = true
self.updateUserInfo(completer: nil)
VPStatAPI.requestStatOnLine()
VPStatAPI.requestEnterApp()
completer?(true)
NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil)
@ -80,12 +83,15 @@ class VPLoginManager: NSObject {
///
func deleteAccount(completer: ((_ isFinish: Bool) -> Void)?) {
VPStatAPI.requestLeaveApp()
VPUserAPI.requestDelete { [weak self] isFinish in
guard let self = self else { return }
if isFinish {
self.setLoginToken(token: nil)
self.userInfo?.is_tourist = true
self.updateUserInfo(completer: nil)
VPStatAPI.requestStatOnLine()
VPStatAPI.requestEnterApp()
completer?(true)
NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil)
@ -120,7 +126,7 @@ extension VPLoginManager {
completer?(false)
return
}
VPStatAPI.requestLeaveApp()
VPUserAPI.requestThirdLogin(model: thirdSignModel) { [weak self] token in
guard let self = self else { return }
guard let token = token else {
@ -130,6 +136,8 @@ extension VPLoginManager {
self.setLoginToken(token: token)
self.userInfo?.is_tourist = false
self.updateUserInfo(completer: nil)
VPStatAPI.requestStatOnLine()
VPStatAPI.requestEnterApp()
completer?(true)
NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil)

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -37,6 +37,7 @@
"Feedback Details" = "Feedback Details";
"Rewards" = "Rewards";
"Join VIP" = "Join VIP";
"VIP Active" = "VIP Active";
"Conins" = "Conins";
"Donate" = "Donate";
"Wallet" = "Wallet";
@ -87,13 +88,16 @@
"Success" = "Success";
"Restore" = "Restore";
"Watch Now" = "Watch Now";
"More" = "More";
"kDetailRecommandTitle" = "Picked Just for You";
"kHomeTitleText" = "10,000+ addictive shorts await!";
"kSearchPlaceholderText1" = "Search dramas";
"kSearchPlaceholderText2" = "#Recersal of fate";
"kHomeMenuTitle" = "Select Categories";
"kVipTipText1" = "unlock 1000+ exclusive dramas";
"kVipTipText1" = "unlock exclusive dramas";
"kVipTipText2" = "vip expires: %@";
"kVipPrivilegeText1" = "Ad-Free\nStreaming";
"kVipPrivilegeText2" = "Exclusive\nEpisodes";
"kVipPrivilegeText3" = "Daily free\ncoins";
@ -104,6 +108,7 @@
"kVideoLockTipText" = "Please unlock the previous episode";
//无网提示
"kNetworkToast01" = "The service is abnormal. Check the network.";
"kNetworkToast02" = "Your account is already logged in on another device~";
//解锁上一集提示
"kLockPreviousEpisodeText" = "The prequel to this series is not unlocked. Please unlock the prequel before unlocking this series";
//解锁失败

View File

@ -8,7 +8,8 @@
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:example.com</string>
<string>applinks:qjwl168.com</string>
<string>applinks:veloriaapp.go.link</string>
</array>
<key>keychain-access-groups</key>
<array/>