首页开发完成

This commit is contained in:
zjx 2025-05-22 15:06:33 +08:00
parent a687f01794
commit b0e84591d9
44 changed files with 1627 additions and 52 deletions

View File

@ -63,6 +63,20 @@
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */; };
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */; };
BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */; };
BF0FA7282DDC91F800C9E5F2 /* VPCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */; };
BF0FA72A2DDC922F00C9E5F2 /* VPHomeRecommandCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7292DDC922F00C9E5F2 /* VPHomeRecommandCell.swift */; };
BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA72B2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift */; };
BF0FA72E2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA72D2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift */; };
BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA72F2DDEBB1600C9E5F2 /* UIButton+VPAdd.swift */; };
BF0FA7322DDEBD6400C9E5F2 /* AppDelegate+Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7312DDEBD6400C9E5F2 /* AppDelegate+Config.swift */; };
BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7332DDEC74500C9E5F2 /* VPCategoryModel.swift */; };
BF0FA7372DDECC5400C9E5F2 /* PlayfairDisplay-VariableFont_wght.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BF0FA7362DDECC5400C9E5F2 /* PlayfairDisplay-VariableFont_wght.ttf */; };
BF0FA7392DDECF8900C9E5F2 /* VPHomeListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7382DDECF8900C9E5F2 /* VPHomeListViewController.swift */; };
BF0FA73B2DDED1C700C9E5F2 /* VPHomeListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA73A2DDED1C700C9E5F2 /* VPHomeListCell.swift */; };
BF0FA73D2DDED2D000C9E5F2 /* VPVideoAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA73C2DDED2D000C9E5F2 /* VPVideoAPI.swift */; };
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */; };
BF0FA7412DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */; };
BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */; };
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
/* End PBXBuildFile section */
@ -132,6 +146,20 @@
BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+VPAdd.swift"; sourceTree = "<group>"; };
BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeRecommandContentCell.swift; sourceTree = "<group>"; };
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCollectionView.swift; sourceTree = "<group>"; };
BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCollectionViewCell.swift; sourceTree = "<group>"; };
BF0FA7292DDC922F00C9E5F2 /* VPHomeRecommandCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeRecommandCell.swift; sourceTree = "<group>"; };
BF0FA72B2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeRankingContentCell.swift; sourceTree = "<group>"; };
BF0FA72D2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeRankingCell.swift; sourceTree = "<group>"; };
BF0FA72F2DDEBB1600C9E5F2 /* UIButton+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+VPAdd.swift"; sourceTree = "<group>"; };
BF0FA7312DDEBD6400C9E5F2 /* AppDelegate+Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Config.swift"; sourceTree = "<group>"; };
BF0FA7332DDEC74500C9E5F2 /* VPCategoryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCategoryModel.swift; sourceTree = "<group>"; };
BF0FA7362DDECC5400C9E5F2 /* PlayfairDisplay-VariableFont_wght.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "PlayfairDisplay-VariableFont_wght.ttf"; sourceTree = "<group>"; };
BF0FA7382DDECF8900C9E5F2 /* VPHomeListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeListViewController.swift; sourceTree = "<group>"; };
BF0FA73A2DDED1C700C9E5F2 /* VPHomeListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeListCell.swift; sourceTree = "<group>"; };
BF0FA73C2DDED2D000C9E5F2 /* VPVideoAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoAPI.swift; sourceTree = "<group>"; };
BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeSearchButton.swift; sourceTree = "<group>"; };
BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+VPRefresh.swift"; sourceTree = "<group>"; };
BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPPlayer.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 */
@ -183,6 +211,7 @@
children = (
1B056E212DDAC0FD007EE38D /* AppDelegate.swift */,
1B056E282DDAC0FD007EE38D /* SceneDelegate.swift */,
BF0FA7312DDEBD6400C9E5F2 /* AppDelegate+Config.swift */,
);
path = AppDelegate;
sourceTree = "<group>";
@ -190,6 +219,7 @@
1B056E332DDAC1AB007EE38D /* Source */ = {
isa = PBXGroup;
children = (
BF0FA7352DDECC4600C9E5F2 /* Font */,
1B056E222DDAC0FD007EE38D /* Assets.xcassets */,
1B056E232DDAC0FD007EE38D /* Info.plist */,
1B056E252DDAC0FD007EE38D /* LaunchScreen.storyboard */,
@ -202,6 +232,7 @@
1B056E352DDAC1DE007EE38D /* Libs */ = {
isa = PBXGroup;
children = (
BF0FA7432DDF025A00C9E5F2 /* Player */,
BF0FA6F22DDC603200C9E5F2 /* HUD */,
BF0FA6D82DDC5C8200C9E5F2 /* Login */,
1B056E652DDAD098007EE38D /* LocalizedManager */,
@ -214,6 +245,7 @@
isa = PBXGroup;
children = (
1B056E522DDACBF4007EE38D /* Home */,
BF0FA7422DDF024400C9E5F2 /* Explore */,
BF0FA6FD2DDC65F300C9E5F2 /* Player */,
);
path = Class;
@ -250,6 +282,7 @@
BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */,
BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */,
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */,
BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */,
);
path = View;
sourceTree = "<group>";
@ -302,6 +335,8 @@
BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */,
BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */,
BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */,
BF0FA72F2DDEBB1600C9E5F2 /* UIButton+VPAdd.swift */,
BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */,
);
path = Extension;
sourceTree = "<group>";
@ -329,6 +364,7 @@
isa = PBXGroup;
children = (
1B056E562DDACC6B007EE38D /* VPHomePageViewController.swift */,
BF0FA7382DDECF8900C9E5F2 /* VPHomeListViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
@ -416,6 +452,7 @@
isa = PBXGroup;
children = (
BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */,
BF0FA73C2DDED2D000C9E5F2 /* VPVideoAPI.swift */,
);
path = API;
sourceTree = "<group>";
@ -426,6 +463,7 @@
BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */,
BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */,
BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */,
BF0FA7332DDEC74500C9E5F2 /* VPCategoryModel.swift */,
);
path = Model;
sourceTree = "<group>";
@ -433,6 +471,8 @@
BF0FA6FD2DDC65F300C9E5F2 /* Player */ = {
isa = PBXGroup;
children = (
BF0FA7472DDF03B600C9E5F2 /* Controller */,
BF0FA7462DDF03AD00C9E5F2 /* View */,
BF0FA6FE2DDC660300C9E5F2 /* Model */,
);
path = Player;
@ -461,7 +501,12 @@
BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */,
BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */,
BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */,
BF0FA72B2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift */,
BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */,
BF0FA7292DDC922F00C9E5F2 /* VPHomeRecommandCell.swift */,
BF0FA72D2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift */,
BF0FA73A2DDED1C700C9E5F2 /* VPHomeListCell.swift */,
BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */,
);
path = View;
sourceTree = "<group>";
@ -483,6 +528,43 @@
path = JXButton;
sourceTree = "<group>";
};
BF0FA7352DDECC4600C9E5F2 /* Font */ = {
isa = PBXGroup;
children = (
BF0FA7362DDECC5400C9E5F2 /* PlayfairDisplay-VariableFont_wght.ttf */,
);
path = Font;
sourceTree = "<group>";
};
BF0FA7422DDF024400C9E5F2 /* Explore */ = {
isa = PBXGroup;
children = (
);
path = Explore;
sourceTree = "<group>";
};
BF0FA7432DDF025A00C9E5F2 /* Player */ = {
isa = PBXGroup;
children = (
BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */,
);
path = Player;
sourceTree = "<group>";
};
BF0FA7462DDF03AD00C9E5F2 /* View */ = {
isa = PBXGroup;
children = (
);
path = View;
sourceTree = "<group>";
};
BF0FA7472DDF03B600C9E5F2 /* Controller */ = {
isa = PBXGroup;
children = (
);
path = Controller;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -548,6 +630,7 @@
files = (
1B056E2E2DDAC0FD007EE38D /* Assets.xcassets in Resources */,
1B056E642DDAD035007EE38D /* Localizable.strings in Resources */,
BF0FA7372DDECC5400C9E5F2 /* PlayfairDisplay-VariableFont_wght.ttf in Resources */,
1B056E302DDAC0FD007EE38D /* LaunchScreen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -604,6 +687,8 @@
1B056E742DDB2DD7007EE38D /* UIView+VPAdd.swift in Sources */,
BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */,
1B056E572DDACC6B007EE38D /* VPHomePageViewController.swift in Sources */,
BF0FA72A2DDC922F00C9E5F2 /* VPHomeRecommandCell.swift in Sources */,
BF0FA7322DDEBD6400C9E5F2 /* AppDelegate+Config.swift in Sources */,
BF0FA6DF2DDC5E4D00C9E5F2 /* String+VPAdd.swift in Sources */,
BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */,
1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */,
@ -616,22 +701,30 @@
1B056E592DDACD44007EE38D /* VPTabBarItemContentView.swift in Sources */,
1B056E2B2DDAC0FD007EE38D /* AppDelegate.swift in Sources */,
1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */,
BF0FA72E2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift in Sources */,
BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */,
1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */,
BF0FA7282DDC91F800C9E5F2 /* VPCollectionViewCell.swift in Sources */,
BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */,
BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */,
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */,
BF0FA73B2DDED1C700C9E5F2 /* VPHomeListCell.swift in Sources */,
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */,
1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */,
1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */,
BF0FA73D2DDED2D000C9E5F2 /* VPVideoAPI.swift in Sources */,
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */,
BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */,
BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */,
1B056E4D2DDAC7C1007EE38D /* VPTabBarController.swift in Sources */,
BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */,
BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */,
BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */,
1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */,
1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */,
BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */,
BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */,
BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */,
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */,
BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */,
1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */,
@ -644,14 +737,17 @@
1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */,
BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */,
1B056E442DDAC355007EE38D /* UIDevice+VPAdd.swift in Sources */,
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */,
1B056E512DDACBE5007EE38D /* VPViewController.swift in Sources */,
BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */,
BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */,
BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */,
BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */,
BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */,
BF0FA7392DDECF8900C9E5F2 /* VPHomeListViewController.swift in Sources */,
BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */,
1B056E492DDAC3DF007EE38D /* VPAppTool.swift in Sources */,
BF0FA7412DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift in Sources */,
BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */,
BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */,
);

View File

@ -0,0 +1,16 @@
//
// AppDelegate+Config.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
extension AppDelegate {
func appConfig() {
UIButton.vp_Awake()
}
}

View File

@ -10,10 +10,12 @@ import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// https://api-qjwl168.qjwl168.com/velo
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
appConfig()
return true
}

View File

@ -87,7 +87,7 @@ extension VPTabBarController {
let nav1 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
let nav2 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
let nav2 = createNavigationController(viewController: VPHomePageViewController(), title: "Explore".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
let nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
let nav4 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))

View File

@ -27,7 +27,9 @@ class VPViewController: UIViewController {
}
}
func handleHeaderRefresh(_ completer: (() -> Void)?) {}
func handleFooterRefresh(_ completer: (() -> Void)?) {}
}

View File

@ -29,3 +29,20 @@ public func vpLog(message:Any? , file: String = #file, function: String = #funct
#else
public func vpLog(message:Any?) { }
#endif
public func vp_swizzled_instanceMethod(_ prefix: String, oldClass: Swift.AnyClass!, oldSelector: String, newClass: Swift.AnyClass) {
let newSelector = prefix + "_" + oldSelector;
let originalSelector = NSSelectorFromString(oldSelector)
let swizzledSelector = NSSelectorFromString(newSelector)
let originalMethod = class_getInstanceMethod(oldClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(newClass, swizzledSelector)
let isAdd = class_addMethod(oldClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
if isAdd {
class_replaceMethod(newClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
}else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}

View File

@ -0,0 +1,118 @@
//
// UIButton+VPAdd.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
extension UIButton {
fileprivate struct AssociatedKeys {
static var vp_gradientLayer: Int?
static var vp_gradientBorder: Int?
static var vp_gradientBorderShapeLayer: Int?
}
@objc public static func vp_Awake() {
vp_swizzled_instanceMethod("vp", oldClass: self, oldSelector: "layoutSubviews", newClass: self)
}
@objc func vp_layoutSubviews() {
vp_layoutSubviews()
vp_gradientLayer?.frame = self.bounds
vp_gradientBorder?.frame = self.bounds
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: 2, dy: 2), cornerRadius: layer.cornerRadius)
vp_gradientBorderShapeLayer?.path = path.cgPath
}
}
extension UIButton {
private var vp_gradientLayer: CAGradientLayer? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientLayer) as? CAGradientLayer
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var vp_gradientBorder: CAGradientLayer? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientBorder) as? CAGradientLayer
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientBorder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var vp_gradientBorderShapeLayer: CAShapeLayer? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientBorderShapeLayer) as? CAShapeLayer
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientBorderShapeLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
///
func vp_setGradient() {
if vp_gradientLayer == nil {
let gLayer = CAGradientLayer()
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
gLayer.locations = [0, 0.8]
gLayer.startPoint = .init(x: 0, y: 0.3)
gLayer.endPoint = .init(x: 1, y: 0.8)
vp_gradientLayer = gLayer
}
if let layer = vp_gradientLayer {
self.layer.addSublayer(layer)
self.titleLabel?.layer.zPosition = 1
}
}
///
func vp_removeGradient() {
vp_gradientLayer?.removeFromSuperlayer()
}
///
func vp_setGradientBorder() {
if vp_gradientBorder == nil {
let gLayer = CAGradientLayer()
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
gLayer.locations = [0, 0.8]
gLayer.startPoint = .init(x: 0, y: 0.3)
gLayer.endPoint = .init(x: 1, y: 0.8)
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 1
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
gLayer.mask = shapeLayer
vp_gradientBorderShapeLayer = shapeLayer
vp_gradientBorder = gLayer
}
if let layer = vp_gradientBorder {
self.layer.addSublayer(layer)
self.titleLabel?.layer.zPosition = 1
}
}
///
func vp_removeGradientBorder() {
vp_gradientBorder?.removeFromSuperlayer()
}
}

View File

@ -41,4 +41,12 @@ extension UIColor {
static func colorAFAFAF(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xAFAFAF, alpha: alpha)
}
static func colorDEDEDE(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xDEDEDE, alpha: alpha)
}
static func colorFFFFFB(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFFFFFB, alpha: alpha)
}
}

View File

@ -31,4 +31,8 @@ extension UIFont {
static func fontBold(ofSize: CGFloat) -> UIFont {
return .systemFont(ofSize: ofSize, weight: .bold)
}
static func numberFont(ofSize: CGFloat) -> UIFont {
return UIFont(name: "Playfair Display", size: ofSize) ?? fontMedium(ofSize: ofSize)
}
}

View File

@ -0,0 +1,59 @@
//
// UIScrollView+VPRefresh.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
extension UIScrollView {
func vp_addRefreshHeader(insetTop: CGFloat = 0, block: (() -> Void)?) {
self.mj_header = MJRefreshNormalHeader(refreshingBlock: {
block?()
})
self.mj_header?.ignoredScrollViewContentInsetTop = insetTop
}
func vp_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) {
let footer = MJRefreshAutoNormalFooter(refreshingBlock: {
block?()
})
footer.ignoredScrollViewContentInsetBottom = insetBottom
self.mj_footer = footer
}
func vp_addRefreshBackFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) {
self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: {
block?()
})
self.mj_footer?.ignoredScrollViewContentInsetBottom = insetBottom
}
func vp_endHeaderRefreshing() {
self.mj_header?.endRefreshing()
}
func vp_endFooterRefreshing() {
if self.mj_footer?.state == .noMoreData { return }
self.mj_footer?.endRefreshing()
}
///
func vp_resetNoMoreData() {
self.mj_footer?.resetNoMoreData()
}
func vp_endRefreshingWithNoMoreData() {
// self.mj_footer?.state = .noMoreData
self.mj_footer?.endRefreshingWithNoMoreData()
}
}

View File

@ -0,0 +1,29 @@
//
// VPVideoAPI.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
class VPVideoAPI: NSObject {
///
static func requestCategoryVideoList(id: String, page: Int, completer: ((_ listModel: VPListModel<VPShortModel>?) -> Void)?) {
var param = VPNetworkParameters(path: "/videoList")
param.method = .get
param.parameters = [
"category_id" : id,
"current_page" : page,
"page_size" : 20
]
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPShortModel>>) in
completer?(response.data)
}
}
}

View File

@ -0,0 +1,20 @@
//
// VPCollectionViewCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.rasterizationScale = UIScreen.main.scale
self.layer.shouldRasterize = true
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}

View File

@ -0,0 +1,119 @@
//
// VPHomeListViewController.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
class VPHomeListViewController: VPViewController, WMZPageProtocol {
var categoryId: String?
private lazy var page: Int = 1
private lazy var dataArr: [VPShortModel] = []
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let width = floor((UIScreen.width - 30 - 14) / 3)
let height = 148 / 110 * width + 31
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: width, height: height)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 7
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
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.customTabBarHeight + 10, right: 0)
collectionView.vp_addRefreshBackFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
collectionView.register(VPHomeListCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
bgImageView.isHidden = true
view.backgroundColor = .clear
vp_setupUI()
requestDataArr(page: 1, completer: nil)
}
func getMyScrollView() -> UIScrollView {
return collectionView
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
requestDataArr(page: self.page + 1) { [weak self] in
self?.collectionView.vp_endFooterRefreshing()
completer?()
}
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
requestDataArr(page: 1, completer: completer)
}
}
extension VPHomeListViewController {
private func vp_setupUI() {
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension VPHomeListViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPHomeListCell
cell.model = dataArr[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataArr.count
}
}
extension VPHomeListViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
guard let id = self.categoryId else { return }
VPVideoAPI.requestCategoryVideoList(id: id, page: page) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.collectionView.reloadData()
}
completer?()
}
}
}

View File

@ -14,9 +14,12 @@ class VPHomePageViewController: VPViewController {
private lazy var pageParam: WMZPageParam = {
let param = WMZPageParam()
param.wTitleArr = ["All".localized, "Romance", "Revenge"]
param.wTitleArr = self.viewModel.categoryTitleList
param.wViewController = { [weak self] index in
return VPViewController()
let categoryModel = self?.viewModel.categoryList[index]
let vc = VPHomeListViewController()
vc.categoryId = categoryModel?.category_id
return vc
}
param.wTopSuspension = true
@ -26,16 +29,23 @@ class VPHomePageViewController: VPViewController {
param.wMenuTitleSelectColor = .colorFFFFFF()
param.wMenuTitleUIFont = .fontRegular(ofSize: 12)
param.wMenuTitleSelectUIFont = .fontMedium(ofSize: 12)
param.wMenuTitleRadios = 17
param.wMenuHeight = 34
param.wMenuTitleOffset = 9
param.wMenuInsets = UIEdgeInsets(top: 42, left: 15, bottom: 17, right: 15)
param.wMenuBgColor = .clear
param.wBgColor = .clear
param.wCustomMenuTitle = { [weak self] buttons in
vpLog(message: "++++++++++\(buttons ?? [])")
buttons?.forEach({ button in
self?.setButtonState(button: button)
})
}
param.wCustomMenuSelectTitle = { buttons in
vpLog(message: "----------\(buttons ?? [])")
param.wCustomMenuSelectTitle = { [weak self] buttons in
buttons?.forEach({ button in
self?.setButtonState(button: button)
})
}
param.wCustomNaviBarY = { _ in
@ -44,35 +54,79 @@ class VPHomePageViewController: VPViewController {
param.wCustomTabbarY = { _ in
return 0
}
param.wCustomDataViewTopOffset = 0
return param
}()
private lazy var pageView: WMZPageView = {
let y = UIScreen.statusBarHeight
let y = UIScreen.statusBarHeight + 115
let width = UIScreen.width
let height = UIScreen.height - y
let pageView = WMZPageView(frame: CGRect(x: 0, y: y, width: width, height: height), param: pageParam, parentReponder: self)
pageView.param = pageParam
pageView.backgroundColor = .clear
pageView.downSc?.backgroundColor = .clear
// pageView.downSc?.delegate = self
pageView.downSc?.dataSource = self
pageView.downSc?.isScrollEnabled = true
pageView.downSc?.separatorStyle = .none
pageView.downSc?.register(VPHomeItemContentCell.self, forCellReuseIdentifier: "cell")
pageView.downSc?.register(VPHomeBannerContentCell.self, forCellReuseIdentifier: VPHomeBannerContentCell.moduleKey.rawValue)
pageView.downSc?.register(VPHomeRecommandContentCell.self, forCellReuseIdentifier: VPHomeRecommandContentCell.moduleKey.rawValue)
// pageView.downSc?.rowHeight = UITableView.automaticDimension
// pageView.downSc?.estimatedRowHeight = 100
pageView.downSc?.register(VPHomeRankingContentCell.self, forCellReuseIdentifier: VPHomeRankingContentCell.moduleKey.rawValue)
pageView.downSc?.vp_addRefreshHeader(block: { [weak self] in
self?.handleHeaderRefresh(nil)
})
return pageView
}()
private lazy var menuTitleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 17)
label.textColor = .colorFFFFFF()
label.text = "kHomeMenuTitle".localized
return label
}()
private lazy var menuBgView: UIView = {
let view = UIImageView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 94))
view.image = UIImage(named: "menu_bg_image_01")
view.addSubview(menuTitleLabel)
menuTitleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(9)
}
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 16)
label.textColor = .colorFFFFFF()
label.text = "kHomeTitleText".localized
return label
}()
private lazy var searchButton: VPHomeSearchButton = {
let button = VPHomeSearchButton()
return button
}()
private lazy var historyButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "history_icon_01"), for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
requestHomeData()
setupPageView()
vp_setupUI()
}
@ -81,22 +135,67 @@ class VPHomePageViewController: VPViewController {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
private func createView(title: String) -> WMZPageNaviBtn {
let button = WMZPageNaviBtn(type: .custom)
button.setTitle(title, for: .normal)
button.setTitleColor(.red, for: .normal)
button.setTitleColor(.green, for: .selected)
return button
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
if let vc = self.pageView.upSc.currentVC as? VPViewController {
vc.handleHeaderRefresh { [weak self] in
self?.pageView.downSc?.vp_endHeaderRefreshing()
}
} else {
self.pageView.downSc?.vp_endHeaderRefreshing()
}
}
private func setButtonState(button: UIButton) {
button.layer.masksToBounds = true
if button.isSelected {
button.vp_setGradient()
button.vp_removeGradientBorder()
} else {
button.vp_removeGradient()
button.vp_setGradientBorder()
}
}
private func setupPageView() {
self.pageView.downSc?.backgroundColor = .clear
self.pageView.backgroundColor = .clear
self.pageView.upSc.addSubview(menuBgView)
self.pageView.upSc.sendSubviewToBack(menuBgView)
}
}
extension VPHomePageViewController {
private func vp_setupUI() {
view.addSubview(titleLabel)
view.addSubview(searchButton)
view.addSubview(historyButton)
view.addSubview(pageView)
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 28)
}
searchButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalTo(historyButton.snp.left).offset(-10)
make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 65)
make.height.equalTo(40)
}
historyButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.width.height.equalTo(40)
make.centerY.equalTo(searchButton)
}
}
}
@ -106,7 +205,6 @@ extension VPHomePageViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.viewModel.newModuleList[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: item.module_key?.rawValue ?? "cell") as! VPHomeItemContentCell
cell.item = item
return cell
@ -129,6 +227,10 @@ extension VPHomePageViewController {
guard let self = self else { return }
if let list = list {
self.viewModel.oldModuleList = list
self.pageParam.wTitleArr = self.viewModel.categoryTitleList
self.pageView.updateMenuData()
self.setupPageView()
self.pageView.downSc?.reloadData()
}
}

View File

@ -0,0 +1,15 @@
//
// VPCategoryModel.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
import SmartCodable
class VPCategoryModel: VPModel, SmartCodable {
var category_name: String?
var category_id: String?
}

View File

@ -13,12 +13,15 @@ class VPHomeModuleItem: VPModel, SmartCodable {
enum ModuleKey: String, SmartCaseDefaultable {
case banner = "home_banner"
case v3_recommand = "home_v3_recommand"
case week_ranking = "week_ranking"
case category_navigation = "category_navigation"
}
var module_key: ModuleKey?
var title: String?
var list: [VPShortModel]?
var categoryList: [VPCategoryModel]?
@SmartAny
var data: Any?
@ -26,7 +29,11 @@ class VPHomeModuleItem: VPModel, SmartCodable {
func didFinishMapping() {
if let data = data as? [[String : Any]] {
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] {
title = data["title"] as? String
list = [VPShortModel].deserialize(from: data["list"] as? [[String : Any]])

View File

@ -51,6 +51,7 @@ class VPHomeBannerCell: ZKCycleScrollViewCell {
button.jx_font = .fontRegular(ofSize: 10)
button.setTitleColor(.colorAFAFAF(), for: .normal)
button.setImage(UIImage(named: "hot_icon_01"), for: .normal)
button.space = 1
return button
}()

View File

@ -0,0 +1,64 @@
//
// VPHomeListCell.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
class VPHomeListCell: VPCollectionViewCell {
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
titleLabel.text = model?.name
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeListCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-31)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(4)
make.right.lessThanOrEqualToSuperview()
}
}
}

View File

@ -0,0 +1,146 @@
//
// VPHomeRankingCell.swift
// Veloria
//
// Created by Veloria on 2025/5/21.
//
import UIKit
class VPHomeRankingCell: VPCollectionViewCell {
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
titleLabel.text = model?.name
if let category = model?.category?.first, !category.isEmpty {
tagView.isHidden = false
tagView.setTitle(category, for: .normal)
} else {
tagView.isHidden = true
}
let watchCount = model?.watch_total ?? 0
if watchCount > 1000 {
let numStr = NSNumber(floatLiteral: CGFloat(watchCount) / 1000).toString(maximumFractionDigits: 1)
hotView.setTitle("\(numStr)K", for: .normal)
} else {
hotView.setTitle("\(watchCount)", for: .normal)
}
}
}
var row: Int = 0 {
didSet {
numLabel.text = "\(row + 1)"
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
imageView.layer.cornerRadius = 6
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
private lazy var tagView: JXButton = {
let view = JXButton(type: .custom)
view.isUserInteractionEnabled = false
view.jx_font = .fontRegular(ofSize: 10)
view.setTitleColor(.colorAFAFAF(), for: .normal)
view.backgroundColor = .colorFFFFFF(alpha: 0.1)
view.layer.cornerRadius = 3
view.layer.masksToBounds = true
view.leftAndRightMargin = 5
return view
}()
private lazy var hotView: UIButton = {
let button = JXButton(type: .custom)
button.isUserInteractionEnabled = false
button.jx_font = .fontRegular(ofSize: 10)
button.setTitleColor(.colorDEDEDE(), for: .normal)
button.setImage(UIImage(named: "hot_icon_01"), for: .normal)
button.space = 1
return button
}()
private lazy var numView: UIView = {
let view = UIImageView(image: UIImage(named: "num_icon_01"))
return view
}()
private lazy var numLabel: UILabel = {
let label = UILabel()
label.textColor = .colorFFFFFB()
label.font = .numberFont(ofSize: 18)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeRankingCell {
private func vp_setupUI() {
contentView.addSubview(numView)
contentView.addSubview(numLabel)
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
contentView.addSubview(tagView)
contentView.addSubview(hotView)
numView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(2)
}
numLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.centerY.equalToSuperview().offset(3)
}
coverImageView.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.equalToSuperview().offset(20)
make.width.equalTo(68)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(10)
make.centerY.equalTo(coverImageView.snp.top).offset(21)
make.right.lessThanOrEqualToSuperview()
}
tagView.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.top.equalToSuperview().offset(43)
make.height.equalTo(16)
}
hotView.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.bottom.equalToSuperview()
}
}
}

View File

@ -0,0 +1,108 @@
//
// VPHomeRankingContentCell.swift
// Veloria
//
// Created by Veloria on 2025/5/21.
//
import UIKit
class VPHomeRankingContentCell: VPHomeItemContentCell {
override class var moduleKey: VPHomeModuleItem.ModuleKey {
return .week_ranking
}
override var item: VPHomeModuleItem? {
didSet {
collectionView.reloadData()
}
}
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "ranking_icon_01"))
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.textColor = .colorFFFFFF()
label.text = "Drama Champions".localized
return label
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 216, height: 90)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 14
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
return layout
}()
private lazy var collectionView: UICollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(VPHomeRankingCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeRankingContentCell {
private func vp_setupUI() {
containerView.addSubview(iconImageView)
containerView.addSubview(titleLabel)
containerView.addSubview(collectionView)
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalTo(iconImageView)
make.left.equalTo(iconImageView.snp.right).offset(3)
}
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(32)
make.height.equalTo(collectionViewLayout.itemSize.height * 3 + collectionViewLayout.minimumInteritemSpacing * 2)
}
}
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
extension VPHomeRankingContentCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = item?.list?[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPHomeRankingCell
cell.model = model
cell.row = indexPath.row
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return item?.list?.count ?? 0
}
}

View File

@ -0,0 +1,112 @@
//
// VPHomeRecommandCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeRecommandCell: VPCollectionViewCell {
enum CellPoint {
case start
case end
case other
}
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
titleLabel.text = model?.name
}
}
var cellPoint = CellPoint.other {
didSet {
applyDiagonalMask()
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func applyDiagonalMask() {
let maskLayer = CAShapeLayer()
let path = UIBezierPath()
let width = 137.0
let height = 152.0
let cut: CGFloat = 19.5 //
let radius: CGFloat = 14 //
if cellPoint == .start {
path.move(to: CGPoint(x: 0, y: radius))
path.addQuadCurve(to: CGPoint(x: radius, y: 0), controlPoint: CGPoint(x: 0, y: 0))
} else {
path.move(to: CGPoint(x: cut - 2, y: radius))
path.addQuadCurve(to: CGPoint(x: cut + radius, y: 0), controlPoint: CGPoint(x: cut, y: 0))
}
//
path.addLine(to: CGPoint(x: width - radius, y: 0))
path.addQuadCurve(to: CGPoint(x: width, y: radius), controlPoint: CGPoint(x: width, y: 0))
//
if cellPoint == .end {
path.addLine(to: CGPoint(x: width, y: height - radius))
path.addQuadCurve(to: CGPoint(x: width - radius, y: height), controlPoint: CGPoint(x: width, y: height))
} else {
path.addLine(to: CGPoint(x: width - cut, y: height - radius))
path.addQuadCurve(to: CGPoint(x: width - cut - radius, y: height), controlPoint: CGPoint(x: width - cut, y: height))
}
//
path.addLine(to: CGPoint(x: radius, y: height))
path.addQuadCurve(to: CGPoint(x: 0, y: height - radius), controlPoint: CGPoint(x: 0, y: height))
path.close()
maskLayer.path = path.cgPath
coverImageView.layer.mask = maskLayer
}
}
extension VPHomeRecommandCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.right.equalToSuperview()
make.top.equalToSuperview()
make.height.equalTo(152)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(4)
make.right.lessThanOrEqualToSuperview().offset(-15)
}
}
}

View File

@ -31,11 +31,17 @@ class VPHomeRecommandContentCell: VPHomeItemContentCell {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 138, height: 152 + 30)
layout.minimumLineSpacing = -6
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(VPHomeRecommandCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
@ -73,13 +79,22 @@ extension VPHomeRecommandContentCell {
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
//extension VPHomeRecommandContentCell: UICollectionViewDelegate, UICollectionViewDataSource {
//
// func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
// }
//
// func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return self.item?.list?.count ?? 0
// }
//}
extension VPHomeRecommandContentCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPHomeRecommandCell
cell.model = item?.list?[indexPath.row]
if indexPath.row == 0 {
cell.cellPoint = .start
} else if (self.item?.list?.count ?? 0) - 1 == indexPath.row {
cell.cellPoint = .end
} else {
cell.cellPoint = .other
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.item?.list?.count ?? 0
}
}

View File

@ -0,0 +1,63 @@
//
// VPHomeSearchButton.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
class VPHomeSearchButton: UIControl {
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "search_icon_01"))
return imageView
}()
private lazy var textLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 14)
label.textColor = .colorFFFFFF(alpha: 0.3)
label.text = "kSearchPlaceholderText1".localized
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeSearchButton {
private func vp_setupUI() {
layer.cornerRadius = 6
layer.masksToBounds = true
layer.borderWidth = 0.7
layer.borderColor = UIColor.colorFFFFFF(alpha: 0.1).cgColor
backgroundColor = UIColor.colorFFFFFF(alpha: 0.05)
addSubview(iconImageView)
addSubview(textLabel)
iconImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(10)
}
textLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(6)
}
}
}

View File

@ -9,11 +9,6 @@ import UIKit
class VPHomeViewModel: VPModel {
var homeDataModel: VPHomeDataModel? {
didSet {
updateDataList()
}
}
/// 使
var oldModuleList: [VPHomeModuleItem]? {
@ -24,8 +19,9 @@ class VPHomeViewModel: VPModel {
///
private(set) lazy var newModuleList: [VPHomeModuleItem] = []
private(set) lazy var homeDataList: [VPHomeDataItem] = []
///
private(set) lazy var categoryList: [VPCategoryModel] = []
private(set) lazy var categoryTitleList: [String] = []
///
@ -36,6 +32,21 @@ class VPHomeViewModel: VPModel {
if let key = $0.module_key {
if key == .banner {
newModuleList.insert($0, at: 0)
} else if key == .category_navigation {
categoryList.removeAll()
categoryTitleList.removeAll()
let allCategory = VPCategoryModel()
allCategory.category_name = "All".localized
allCategory.category_id = "0"
categoryList.append(allCategory)
categoryList += ($0.categoryList ?? [])
categoryList.forEach { model in
categoryTitleList.append(model.category_name ?? "")
}
} else {
newModuleList.append($0)
}
@ -43,14 +54,4 @@ class VPHomeViewModel: VPModel {
})
}
private func updateDataList() {
homeDataList.removeAll()
if let bannerData = homeDataModel?.bannerData {
let item = VPHomeDataItem()
item.type = .banner
item.list = bannerData
}
}
}

View File

@ -0,0 +1,326 @@
//
// VPPlayer.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
import ZFPlayer
@objc protocol VPPlayerDelegate {
///
// @objc optional func vp_onDurationUpdate(_ player: SPPlayer, duration: Int)
//
// ///
// @objc optional func vp_onCurrentPositionUpdate(_ player: SPPlayer, position: Int)
///
@objc optional func vp_player(_ player: VPPlayer, playStateDidChanged state: VPPlayer.PlayState)
///
@objc optional func vp_player(_ player: VPPlayer, loadStateDidChange state: VPPlayer.LoadState)
///
@objc optional func vp_playTimeChanged(_ player: VPPlayer, currentTime: Int, duration: Int)
///
@objc optional func vp_firstRenderedStart(_ player: VPPlayer)
///
@objc optional func vp_playerReadyToPlay(_ player: VPPlayer)
///
@objc optional func vp_playCompletion(_ player: VPPlayer)
///
@objc optional func vp_playLoadingEnd(_ player: VPPlayer)
}
class VPPlayer: NSObject {
@objc enum PlayState: Int {
case unknown
case playing
case paused
case failed
case stopped
}
@objc enum LoadState: Int {
case unknown
case prepare
case playable
case playthroughOK
case stalled
}
weak var delegate: VPPlayerDelegate?
private(set) lazy var isPlaying = false
private(set) lazy var playState: PlayState = .unknown
private(set) lazy var loadState: LoadState = .unknown
/**
*/
private var isAddIdleTimerDisabledObserver = false
///
private var interruptionType: AVAudioSession.InterruptionType?
///
var duration: Int {
return Int(self.player.totalTime)
}
///
var currentPosition: Int {
return Int(self.player.currentTime)
}
///0.5 - 2
var rate: Float {
set {
player.rate = newValue
}
get {
return player.rate
}
}
var playerView: UIView? {
didSet {
playerView?.addSubview(player.view)
player.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
private lazy var player: ZFAVPlayerManager = {
let player = ZFAVPlayerManager()
player.shouldAutoPlay = false
return player
}()
var isLoop = true
deinit {
NotificationCenter.default.removeObserver(self)
self.stop()
}
override init() {
super.init()
//()
NotificationCenter.default.addObserver(self, selector: #selector(interruptionNotification(sender:)), name: AVAudioSession.interruptionNotification, object: nil)
//
NotificationCenter.default.addObserver(self, selector: #selector(routeChangeNotification(sender:)), name: AVAudioSession.routeChangeNotification, object: nil)
player.scalingMode = .aspectFill
vp_addAction()
}
/**
*/
private func addIdleTimerDisabledObserver() {
if !isAddIdleTimerDisabledObserver {
isAddIdleTimerDisabledObserver = true
UIApplication.shared.addObserver(self, forKeyPath: "idleTimerDisabled", options: NSKeyValueObservingOptions.new, context: nil)
}
}
/**
*/
private func removeIdleTimerDisabledObserver() {
if isAddIdleTimerDisabledObserver {
isAddIdleTimerDisabledObserver = false
UIApplication.shared.removeObserver(self, forKeyPath: "idleTimerDisabled")
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if !UIApplication.shared.isIdleTimerDisabled {
UIApplication.shared.isIdleTimerDisabled = true
}
}
func setPlayUrl(url: String) {
let proxyURL = KTVHTTPCache.proxyURL(withOriginalURL: URL(string: url))
if proxyURL != self.player.assetURL {
self.player.assetURL = proxyURL
}
// self.prepare()
}
///
func prepare() {
self.player.prepareToPlay()
}
func stop() {
self.isPlaying = false
player.stop()
self.removeIdleTimerDisabledObserver()
UIApplication.shared.isIdleTimerDisabled = false
}
func start() {
self.isPlaying = true
if self.interruptionType != .began {
player.play()
}
UIApplication.shared.isIdleTimerDisabled = true
self.addIdleTimerDisabledObserver()
}
///
func pause() {
self.isPlaying = false
player.pause()
self.removeIdleTimerDisabledObserver()
UIApplication.shared.isIdleTimerDisabled = false
}
///
func replay() {
self.isPlaying = true
self.player.replay()
UIApplication.shared.isIdleTimerDisabled = true
self.addIdleTimerDisabledObserver()
}
func seekToTime(toTime: Int) {
var time = toTime
if time < 0 {
time = 0
}
if time > self.duration {
time = self.duration
}
self.player.seek(toTime: TimeInterval(time), completionHandler: nil)
}
}
extension VPPlayer {
private func vp_addAction() {
//
player.playerPlayTimeChanged = { [weak self] (asset, currentTime, duration) in
guard let self = self else { return }
self.delegate?.vp_playTimeChanged?(self, currentTime: Int(currentTime), duration: Int(duration))
}
//
player.playerPlayStateChanged = { [weak self] (asset, playState) in
guard let self = self else { return }
if playState == .playStatePlaying && !isPlaying {
self.pause()
} else if playState == .playStatePaused, isPlaying, self.interruptionType != .began {
self.start()
}
switch playState {
case .playStateUnknown:
self.playState = .unknown
case .playStatePlaying:
self.playState = .playing
case .playStatePaused:
self.playState = .paused
case .playStatePlayStopped:
self.playState = .stopped
case .playStatePlayFailed:
self.playState = .failed
default:
self.playState = .unknown
}
self.delegate?.vp_player?(self, playStateDidChanged: self.playState)
vpLog(message: "播放状态====\(playState)")
}
//
player.playerLoadStateChanged = { [weak self] (asset, loadState) in
guard let self = self else { return }
if loadState == .playable && !isPlaying {
self.pause()
} else if loadState == .playable, isPlaying, self.player.playState != .playStatePlaying, self.interruptionType != .began {
self.start()
}
switch loadState {
case .prepare:
self.loadState = .prepare
case .playable:
self.loadState = .playable
case .playthroughOK:
self.loadState = .playthroughOK
case .stalled:
self.loadState = .stalled
default:
self.loadState = .unknown
break
}
self.delegate?.vp_player?(self, loadStateDidChange: self.loadState)
}
//
player.playerPlayFailed = { [weak self] (asset, error) in
vpLog(message: "错误信息====\(error)")
}
//
player.playerDidToEnd = { [weak self] (asset) in
guard let self = self else { return }
if isLoop {
self.replay()
} else {
self.isPlaying = false
self.prepare()
self.delegate?.vp_playCompletion?(self)
}
}
player.playerReadyToPlay = { [weak self] (asset, assetURL) in
guard let self = self else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
}
self.delegate?.vp_playerReadyToPlay?(self)
}
}
}
//MARK: -------------- --------------
extension VPPlayer {
@objc private func interruptionNotification(sender: Notification) {
guard let userInfo = sender.userInfo else { return }
guard let interruptionType = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt else { return }
self.interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionType)
if self.interruptionType == .began {
self.player.pause()
} else {
if self.isPlaying {
self.player.play()
}
}
}
@objc private func routeChangeNotification(sender: Notification) {
// self.pause()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 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.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIAppFonts</key>
<array>
<string>PlayfairDisplay-VariableFont_wght.ttf</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>

View File

@ -6,3 +6,6 @@
#import <WMZPageController/WMZPageController.h>
#import "JXUUID.h"
#import <Toast.h>
#import <MJRefresh/MJRefresh.h>
#import <ZFPlayer.h>
#import <KTVHTTPCache/KTVHTTPCache.h>

View File

@ -8,3 +8,11 @@
"Home" = "Home";
"All" = "All";
"Drama Champions" = "Drama Champions";
"Explore" = "Explore";
"kHomeTitleText" = "10,000+ addictive shorts await!";
"kSearchPlaceholderText1" = "Search dramas";
"kHomeMenuTitle" = "Select Categories";