搜索功能完成

This commit is contained in:
zjx 2025-05-24 13:46:23 +08:00
parent 4ced649801
commit 5851400e12
40 changed files with 1883 additions and 10 deletions

View File

@ -97,6 +97,21 @@
BF0FA76F2DE062A700C9E5F2 /* VPRateSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */; };
BF0FA7712DE062EB00C9E5F2 /* VPVideoRateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */; };
BF0FA7732DE0671200C9E5F2 /* VPRateSelectedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */; };
BF0FA7752DE071B500C9E5F2 /* VPSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */; };
BF0FA7772DE0735800C9E5F2 /* VPSearchInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7762DE0735800C9E5F2 /* VPSearchInputView.swift */; };
BF0FA7792DE075FF00C9E5F2 /* VPSearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7782DE075FF00C9E5F2 /* VPSearchHomeView.swift */; };
BF0FA77B2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */; };
BF0FA77D2DE078D400C9E5F2 /* VPSearchRecommendedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA77C2DE078D400C9E5F2 /* VPSearchRecommendedView.swift */; };
BF0FA7812DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7802DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift */; };
BF0FA7832DE1533E00C9E5F2 /* VPGradientLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */; };
BF0FA7852DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7842DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift */; };
BF0FA7872DE1601200C9E5F2 /* VPTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */; };
BF0FA7892DE161F200C9E5F2 /* VPSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7882DE161F200C9E5F2 /* VPSearchResultView.swift */; };
BF0FA78B2DE164C100C9E5F2 /* VPSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78A2DE164C100C9E5F2 /* VPSearchResultCell.swift */; };
BF0FA78D2DE16A8B00C9E5F2 /* VPSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78C2DE16A8B00C9E5F2 /* VPSearchViewModel.swift */; };
BF0FA78F2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */; };
BF0FA7912DE16CBF00C9E5F2 /* VPSearchHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */; };
BF0FA7942DE16E9300C9E5F2 /* JXTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */; };
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
/* End PBXBuildFile section */
@ -200,6 +215,21 @@
BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPRateSelectedView.swift; sourceTree = "<group>"; };
BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoRateModel.swift; sourceTree = "<group>"; };
BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPRateSelectedCell.swift; sourceTree = "<group>"; };
BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchViewController.swift; sourceTree = "<group>"; };
BF0FA7762DE0735800C9E5F2 /* VPSearchInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchInputView.swift; sourceTree = "<group>"; };
BF0FA7782DE075FF00C9E5F2 /* VPSearchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchHomeView.swift; sourceTree = "<group>"; };
BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+VPAdd.swift"; sourceTree = "<group>"; };
BF0FA77C2DE078D400C9E5F2 /* VPSearchRecommendedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchRecommendedView.swift; sourceTree = "<group>"; };
BF0FA7802DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchRecommendedContentView.swift; sourceTree = "<group>"; };
BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPGradientLabel.swift; sourceTree = "<group>"; };
BF0FA7842DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchRecommendedCell.swift; sourceTree = "<group>"; };
BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTextField.swift; sourceTree = "<group>"; };
BF0FA7882DE161F200C9E5F2 /* VPSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchResultView.swift; sourceTree = "<group>"; };
BF0FA78A2DE164C100C9E5F2 /* VPSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchResultCell.swift; sourceTree = "<group>"; };
BF0FA78C2DE16A8B00C9E5F2 /* VPSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchViewModel.swift; sourceTree = "<group>"; };
BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPUserDefaultsKey.swift; sourceTree = "<group>"; };
BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchHistoryView.swift; sourceTree = "<group>"; };
BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXTagView.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 */
@ -284,6 +314,7 @@
1B056E362DDAC1E8007EE38D /* Class */ = {
isa = PBXGroup;
children = (
BF0FA7952DE1948A00C9E5F2 /* MyList */,
1B056E522DDACBF4007EE38D /* Home */,
BF0FA7422DDF024400C9E5F2 /* Explore */,
BF0FA6FD2DDC65F300C9E5F2 /* Player */,
@ -324,6 +355,8 @@
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */,
BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */,
BF0FA76C2DE053C100C9E5F2 /* VPScrollView.swift */,
BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */,
BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */,
);
path = View;
sourceTree = "<group>";
@ -350,6 +383,7 @@
isa = PBXGroup;
children = (
1B056E4A2DDAC6BA007EE38D /* VPDefine.swift */,
BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */,
);
path = Define;
sourceTree = "<group>";
@ -378,6 +412,7 @@
BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */,
BF0FA72F2DDEBB1600C9E5F2 /* UIButton+VPAdd.swift */,
BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */,
BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */,
);
path = Extension;
sourceTree = "<group>";
@ -406,6 +441,7 @@
children = (
1B056E562DDACC6B007EE38D /* VPHomePageViewController.swift */,
BF0FA7382DDECF8900C9E5F2 /* VPHomeListViewController.swift */,
BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
@ -462,6 +498,7 @@
BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */ = {
isa = PBXGroup;
children = (
BF0FA7932DE16E9300C9E5F2 /* JXTagView */,
BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */,
BF0FA7152DDC78FF00C9E5F2 /* ZKCycleScrollView-Swift */,
BF0FA71F2DDC83AE00C9E5F2 /* JXButton */,
@ -536,6 +573,7 @@
isa = PBXGroup;
children = (
BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */,
BF0FA78C2DE16A8B00C9E5F2 /* VPSearchViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -552,6 +590,14 @@
BF0FA72D2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift */,
BF0FA73A2DDED1C700C9E5F2 /* VPHomeListCell.swift */,
BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */,
BF0FA7762DE0735800C9E5F2 /* VPSearchInputView.swift */,
BF0FA7782DE075FF00C9E5F2 /* VPSearchHomeView.swift */,
BF0FA77C2DE078D400C9E5F2 /* VPSearchRecommendedView.swift */,
BF0FA7802DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift */,
BF0FA7842DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift */,
BF0FA7882DE161F200C9E5F2 /* VPSearchResultView.swift */,
BF0FA78A2DE164C100C9E5F2 /* VPSearchResultCell.swift */,
BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */,
);
path = View;
sourceTree = "<group>";
@ -657,6 +703,37 @@
path = Controller;
sourceTree = "<group>";
};
BF0FA7932DE16E9300C9E5F2 /* JXTagView */ = {
isa = PBXGroup;
children = (
BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */,
);
path = JXTagView;
sourceTree = "<group>";
};
BF0FA7952DE1948A00C9E5F2 /* MyList */ = {
isa = PBXGroup;
children = (
BF0FA7972DE1949B00C9E5F2 /* View */,
BF0FA7962DE1949300C9E5F2 /* Controller */,
);
path = MyList;
sourceTree = "<group>";
};
BF0FA7962DE1949300C9E5F2 /* Controller */ = {
isa = PBXGroup;
children = (
);
path = Controller;
sourceTree = "<group>";
};
BF0FA7972DE1949B00C9E5F2 /* View */ = {
isa = PBXGroup;
children = (
);
path = View;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -786,16 +863,20 @@
BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */,
1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */,
BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */,
BF0FA78B2DE164C100C9E5F2 /* VPSearchResultCell.swift in Sources */,
BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */,
BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */,
BF0FA7572DDF159B00C9E5F2 /* VPExploreViewController.swift in Sources */,
1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */,
1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */,
1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */,
BF0FA7752DE071B500C9E5F2 /* VPSearchViewController.swift in Sources */,
BF0FA78D2DE16A8B00C9E5F2 /* VPSearchViewModel.swift in Sources */,
1B056E592DDACD44007EE38D /* VPTabBarItemContentView.swift in Sources */,
1B056E2B2DDAC0FD007EE38D /* AppDelegate.swift in Sources */,
1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */,
BF0FA72E2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift in Sources */,
BF0FA7792DE075FF00C9E5F2 /* VPSearchHomeView.swift in Sources */,
BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */,
BF0FA74C2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift in Sources */,
BF0FA76D2DE053C100C9E5F2 /* VPScrollView.swift in Sources */,
@ -809,12 +890,17 @@
BF0FA7592DDF1C2800C9E5F2 /* VPPlayerProgressView.swift in Sources */,
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */,
BF0FA7522DDF134700C9E5F2 /* VPVideoPlayerControlView.swift in Sources */,
BF0FA7852DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift in Sources */,
BF0FA7832DE1533E00C9E5F2 /* VPGradientLabel.swift in Sources */,
1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */,
BF0FA75D2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift in Sources */,
1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */,
BF0FA7692DE0502900C9E5F2 /* VPEpisodeCell.swift in Sources */,
BF0FA73D2DDED2D000C9E5F2 /* VPVideoAPI.swift in Sources */,
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */,
BF0FA77D2DE078D400C9E5F2 /* VPSearchRecommendedView.swift in Sources */,
BF0FA7812DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift in Sources */,
BF0FA7872DE1601200C9E5F2 /* VPTextField.swift in Sources */,
BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */,
BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */,
BF0FA75F2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift in Sources */,
@ -823,11 +909,13 @@
BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */,
BF0FA74A2DDF04E200C9E5F2 /* VPPlayerProtocol.swift in Sources */,
BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */,
BF0FA7942DE16E9300C9E5F2 /* JXTagView.swift in Sources */,
BF0FA7652DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift in Sources */,
BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */,
1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */,
1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */,
BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */,
BF0FA7912DE16CBF00C9E5F2 /* VPSearchHistoryView.swift in Sources */,
BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */,
BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */,
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */,
@ -836,10 +924,14 @@
BF0FA76B2DE0533400C9E5F2 /* VPEpisodeMenuView.swift in Sources */,
1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */,
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */,
BF0FA78F2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift in Sources */,
BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */,
BF0FA7712DE062EB00C9E5F2 /* VPVideoRateModel.swift in Sources */,
BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */,
BF0FA77B2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift in Sources */,
BF0FA76F2DE062A700C9E5F2 /* VPRateSelectedView.swift in Sources */,
BF0FA7772DE0735800C9E5F2 /* VPSearchInputView.swift in Sources */,
BF0FA7892DE161F200C9E5F2 /* VPSearchResultView.swift in Sources */,
1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */,
1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */,
1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */,

View File

@ -88,7 +88,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: VPExploreViewController(), 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 nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "My List".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"))
viewControllers = [nav1, nav2, nav3, nav4]

View File

@ -0,0 +1,11 @@
//
// VPUserDefaultsKey.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
///
let kVPSearchHistoryDefaultsKey = "kVPSearchHistoryDefaultsKey"

View File

@ -11,6 +11,10 @@ extension UIColor {
static func backgroundColor() -> UIColor {
return color000000()
}
static func placeholderColor() -> UIColor {
return colorFFFFFF(alpha: 0.3)
}
}
extension UIColor {
@ -69,4 +73,28 @@ extension UIColor {
static func color1C2D2F(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x1C2D2F, alpha: alpha)
}
static func colorA87A46(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xA87A46, alpha: alpha)
}
static func colorFBF2C7(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFBF2C7, alpha: alpha)
}
static func colorA8A38E(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xA8A38E, alpha: alpha)
}
static func color555555(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x555555, alpha: alpha)
}
static func colorFFBFBE(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFFBFBE, alpha: alpha)
}
static func colorD87675(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xD87675, alpha: alpha)
}
}

View File

@ -0,0 +1,20 @@
//
// UIStackView+VPAdd.swift
// Veloria
//
// Created by on 2025/5/23.
//
import UIKit
extension UIStackView {
func removeAllArrangedSubview() {
let arrangedSubviews = self.arrangedSubviews
arrangedSubviews.forEach {
self.removeArrangedSubview($0)
$0.removeFromSuperview()
}
}
}

View File

@ -19,4 +19,38 @@ class VPHomeAPI: NSObject {
completer?(response.data?.list)
}
}
///
static func requestHotSearchList(completer: ((_ list: [VPShortModel]?) -> Void)?) {
var param = VPNetworkParameters(path: "/search/hots")
param.method = .get
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPShortModel>>) in
completer?(response.data?.list)
}
}
///
static func requestTopSearchList(completer: ((_ list: [VPShortModel]?) -> Void)?) {
var param = VPNetworkParameters(path: "/getVisitTop")
param.method = .get
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<[VPShortModel]>) in
completer?(response.data)
}
}
///
static func requestSearch(text: String, completer: ((_ list: [VPShortModel]?) -> Void)?) {
var param = VPNetworkParameters(path: "/search")
param.method = .get
param.parameters = [
"search" : text
]
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPShortModel>>) in
completer?(response.data?.list)
}
}
}

View File

@ -0,0 +1,51 @@
//
// VPGradientLabel.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPGradientLabel: UILabel {
private(set) var gradientLayer: CAGradientLayer = {
//
let gradient = CAGradientLayer()
gradient.colors = [
UIColor.red.cgColor,
UIColor.blue.cgColor
]
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 0.5)
gradient.locations = [0, 1]
return gradient
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.addSublayer(gradientLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
//
let textLayer = CATextLayer()
textLayer.string = attributedText ?? NSAttributedString(string: text ?? "")
textLayer.frame = bounds
textLayer.alignmentMode = .center
textLayer.contentsScale = UIScreen.main.scale
gradientLayer.mask = textLayer
}
}

View File

@ -0,0 +1,126 @@
//
// VPTextField.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPTextField: UITextField {
///0
var maxNumber = 0
var currentNumber = 0
var textDidChange: ((_ text: String) -> Void)?
var vp_placeholder: String? {
set {
_createAttributedPlaceholder()
let newStr = NSMutableAttributedString(string: newValue ?? "")
newStr.font = self.vp_placeholderFont
newStr.color = self.vp_placeholderColor
_attributedPlaceholder = newStr
self.attributedPlaceholder = _attributedPlaceholder
}
get {
return _attributedPlaceholder?.string
}
}
var vp_placeholderFont: UIFont? {
set {
_createAttributedPlaceholder()
_attributedPlaceholder?.font = newValue
self.attributedPlaceholder = _attributedPlaceholder
}
get {
if let font = _attributedPlaceholder?.font {
return font
} else {
return self.font
}
}
}
var vp_placeholderColor: UIColor? {
set {
_createAttributedPlaceholder()
_attributedPlaceholder?.color = newValue
self.attributedPlaceholder = _attributedPlaceholder
}
get {
if let color = _attributedPlaceholder?.color {
return color
} else {
return .placeholderColor()
}
}
}
private var _attributedPlaceholder: NSMutableAttributedString?
private func _createAttributedPlaceholder() {
if self._attributedPlaceholder == nil {
_attributedPlaceholder = NSMutableAttributedString(string: "")
_attributedPlaceholder?.font = self.font
_attributedPlaceholder?.color = .placeholderColor()
self.attributedPlaceholder = _attributedPlaceholder
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override init(frame: CGRect) {
super.init(frame: frame)
NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(sender:)), name: UITextField.textDidChangeNotification, object: nil)
// var iq = self.iq
// iq.enableMode = .enabled
self.textColor = .colorFFFFFF()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPTextField {
@objc func textDidChangeNotification(sender: Notification){
guard let object = sender.object else { return }
if !(object is UITextField) {return}
if (object as! UITextField) != self {return}
let selectedRange: UITextRange? = self.markedTextRange
if selectedRange == nil {
if maxNumber > 0 {
if self.text?.count ?? 0 > self.maxNumber {
var string = self.text
string?.removeLast(self.text!.count - self.maxNumber)
self.text = string
}
}
if let textDidChange = textDidChange {
textDidChange(self.text ?? "")
}
self.currentNumber = self.text?.count ?? 0
}
}
}

View File

@ -111,6 +111,7 @@ class VPHomePageViewController: VPViewController {
private lazy var searchButton: VPHomeSearchButton = {
let button = VPHomeSearchButton()
button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside)
return button
}()
@ -164,10 +165,13 @@ class VPHomePageViewController: VPViewController {
self.pageView.upSc.addSubview(menuBgView)
self.pageView.upSc.sendSubviewToBack(menuBgView)
}
@objc private func handleSearchButton() {
let vc = VPSearchViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
}
extension VPHomePageViewController {

View File

@ -0,0 +1,111 @@
//
// VPSearchViewController.swift
// Veloria
//
// Created by on 2025/5/23.
//
import UIKit
class VPSearchViewController: VPViewController {
private lazy var viewModel = VPSearchViewModel()
private lazy var backButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
button.addTarget(self, action: #selector(handleBack), for: .touchUpInside)
return button
}()
private lazy var textView: VPSearchInputView = {
let view = VPSearchInputView()
view.textDidChange = { [weak self] text in
self?.textDidChange(text: text)
}
return view
}()
private lazy var homeView: VPSearchHomeView = {
let view = VPSearchHomeView()
view.viewModel = viewModel
return view
}()
private lazy var resultView: VPSearchResultView = {
let view = VPSearchResultView()
view.viewModel = viewModel
view.isHidden = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
vp_setupUI()
vp_addAction()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
}
extension VPSearchViewController {
///
func textDidChange(text: String) {
if text.count > 0 {
self.resultView.isHidden = false
self.homeView.isHidden = true
} else {
self.resultView.isHidden = true
self.homeView.isHidden = false
}
self.resultView.search(text: text)
}
}
extension VPSearchViewController {
private func vp_setupUI() {
view.addSubview(backButton)
view.addSubview(textView)
view.addSubview(homeView)
view.addSubview(resultView)
backButton.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview().offset(UIScreen.statusBarHeight)
make.width.equalTo(48)
make.height.equalTo(44)
}
textView.snp.makeConstraints { make in
make.left.equalTo(backButton.snp.right)
make.right.equalToSuperview().offset(-15)
make.centerY.equalTo(backButton)
make.height.equalTo(40)
}
homeView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(textView.snp.bottom)
}
resultView.snp.makeConstraints { make in
make.edges.equalTo(homeView)
}
}
private func vp_addAction() {
self.viewModel.searchHandle = { [weak self] text in
self?.textView.text = text
self?.textDidChange(text: text)
}
}
}

View File

@ -0,0 +1,115 @@
//
// VPSearchHistoryView.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPSearchHistoryView: UIView {
var historyArr: [String] = [] {
didSet {
tagView.reloadData()
}
}
var viewModel: VPSearchViewModel?
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 13)
label.textColor = .colorFFFFFF()
label.text = "Recent Searches".localized
return label
}()
private lazy var deleteButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "delete_icon_01"), for: .normal)
button.addTarget(self, action: #selector(handleDeleteButton), for: .touchUpInside)
return button
}()
private lazy var tagView: JXTagView = {
let tagView = JXTagView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 10))
tagView.tagBackgroundColor = .colorFFFFFF(alpha: 0.1)
tagView.tagHeight = 24
tagView.textMargin = 5
tagView.tagCornerRadius = 12
tagView.tagHorizontalGap = 8
tagView.tagVerticalGap = 8
tagView.textFont = .fontRegular(ofSize: 11)
tagView.textColor = .colorFFFFFF(alpha: 0.7)
tagView.delegate = self
tagView.dataSource = self
tagView.setContentHuggingPriority(.required, for: .horizontal)
tagView.setContentCompressionResistancePriority(.required, for: .horizontal)
return tagView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setContentHuggingPriority(.required, for: .horizontal)
setContentCompressionResistancePriority(.required, for: .horizontal)
vp_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleDeleteButton() {
self.viewModel?.cleanHistory()
}
}
extension VPSearchHistoryView {
private func vp_setupUI() {
addSubview(titleLabel)
addSubview(deleteButton)
addSubview(tagView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(20)
}
deleteButton.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.right.equalToSuperview().offset(-5)
make.width.equalTo(36)
make.height.equalTo(40)
}
tagView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(48)
make.bottom.equalToSuperview()
}
}
}
//MARK: -------------- JXTagViewDelegate JXTagViewDataSource --------------
extension VPSearchHistoryView: JXTagViewDelegate, JXTagViewDataSource {
func jx_tagView(tagView: JXTagView, titleForIndex index: Int) -> String? {
return historyArr[index]
}
func jx_number(in tagView: JXTagView) -> Int {
return historyArr.count
}
func jx_tagView(tagView: JXTagView, didSelectedTagAt index: Int) {
self.viewModel?.searchHandle?(historyArr[index])
}
}

View File

@ -0,0 +1,84 @@
//
// VPSearchHomeView.swift
// Veloria
//
// Created by on 2025/5/23.
//
import UIKit
class VPSearchHomeView: UIView {
var viewModel: VPSearchViewModel? {
didSet {
viewModel?.addObserver(self, forKeyPath: "historyArr", context: nil)
historyView.historyArr = viewModel?.historyArr ?? []
updateLayout()
historyView.viewModel = viewModel
}
}
private lazy var stackView: UIStackView = {
let view = UIStackView(arrangedSubviews: [historyView, recommendedView])
view.axis = .vertical
view.spacing = 30
view.distribution = .equalSpacing
return view
}()
private lazy var historyView: VPSearchHistoryView = {
let view = VPSearchHistoryView()
return view
}()
private lazy var recommendedView: VPSearchRecommendedView = {
let view = VPSearchRecommendedView()
return view
}()
deinit {
viewModel?.removeObserver(self, forKeyPath: "historyArr")
}
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "historyArr" {
historyView.historyArr = viewModel?.historyArr ?? []
updateLayout()
}
}
private func updateLayout() {
stackView.removeAllArrangedSubview()
if (viewModel?.historyArr.count ?? 0) > 0 {
stackView.addArrangedSubview(historyView)
}
stackView.addArrangedSubview(recommendedView)
}
}
extension VPSearchHomeView {
private func vp_setupUI() {
addSubview(stackView)
stackView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(20)
make.bottom.equalToSuperview()
}
}
}

View File

@ -0,0 +1,76 @@
//
// VPSearchInputView.swift
// Veloria
//
// Created by on 2025/5/23.
//
import UIKit
class VPSearchInputView: UIView {
var textDidChange: ((_ text: String) -> Void)?
var text: String? {
set {
textField.text = newValue
}
get {
return textField.text
}
}
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "search_icon_01"))
return imageView
}()
private lazy var textField: VPTextField = {
let textField = VPTextField()
textField.font = .fontRegular(ofSize: 14)
textField.vp_placeholder = "kSearchPlaceholderText2".localized
textField.vp_placeholderFont = textField.font
textField.textDidChange = { [weak self] text in
self?.textDidChange?(text)
}
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPSearchInputView {
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(textField)
iconImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(10)
}
textField.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.equalToSuperview().offset(45)
make.right.equalToSuperview().offset(-10)
}
}
}

View File

@ -0,0 +1,142 @@
//
// VPSearchRecommendedCell.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPSearchRecommendedCell: VPCollectionViewCell {
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
titleLabel.text = model?.name
desLabel.text = model?.vp_description
if let category = model?.category?.first, !category.isEmpty {
tagView.isHidden = false
tagView.setTitle(category, for: .normal)
} else {
tagView.isHidden = true
}
}
}
var row: Int = 0 {
didSet {
if row < 3 {
numBgView.image = UIImage(named: "num_icon_01")
} else {
numBgView.image = UIImage(named: "num_icon_02")
}
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 = .fontMedium(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
private lazy var tagView: UIButton = {
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 desLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 2
label.font = .fontRegular(ofSize: 10)
label.textColor = .colorFFFFFF(alpha: 0.6)
return label
}()
private lazy var numBgView: UIImageView = {
let view = UIImageView()
return view
}()
private lazy var numLabel: UILabel = {
let label = UILabel()
label.font = .numberFont(ofSize: 18)
label.textColor = .colorFFFFFB()
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 VPSearchRecommendedCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
coverImageView.addSubview(numBgView)
coverImageView.addSubview(numLabel)
contentView.addSubview(titleLabel)
contentView.addSubview(tagView)
contentView.addSubview(desLabel)
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.bottom.equalToSuperview()
make.width.equalTo(68)
}
numBgView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview()
}
numLabel.snp.makeConstraints { make in
make.centerX.equalTo(numBgView).offset(2)
make.centerY.equalTo(numBgView).offset(2)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(10)
make.right.lessThanOrEqualToSuperview()
make.centerY.equalTo(coverImageView.snp.top).offset(15)
}
tagView.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.top.equalToSuperview().offset(39)
make.height.equalTo(16)
}
desLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.right.lessThanOrEqualToSuperview()
make.bottom.equalToSuperview().offset(-3)
}
}
}

View File

@ -0,0 +1,170 @@
//
// VPSearchRecommendedContentView.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPSearchRecommendedContentView: VPGradientView {
override var intrinsicContentSize: CGSize {
return .init(width: 256, height: 500)
}
var dataArr: [VPShortModel] = [] {
didSet {
self.collectionView.reloadData()
}
}
var iconImage: UIImage? {
didSet {
iconImageView.image = iconImage
}
}
var title: String? {
didSet {
titleLabel.text = title
}
}
var borderColors: [CGColor] = [] {
didSet {
borderGradientLayout.colors = borderColors
}
}
var titleColors: [CGColor] = [] {
didSet {
titleLabel.gradientLayer.colors = titleColors
}
}
///
private lazy var borderGradientLayout: CAGradientLayer = {
let layer = CAGradientLayer()
layer.locations = [0, 0.1, 0.3, 1]
layer.startPoint = .init(x: 0.5, y: 0)
layer.endPoint = .init(x: 0.5, y: 1)
layer.mask = borderShapeLayer
return layer
}()
private lazy var borderShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 1
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
return shapeLayer
}()
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
private lazy var titleLabel: VPGradientLabel = {
let label = VPGradientLabel()
label.font = .fontBold(ofSize: 14)
return label
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: intrinsicContentSize.width - 20, height: 90)
layout.sectionInset = .init(top: 0, left: 10, bottom: 0, right: 10)
layout.minimumLineSpacing = 10
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.register(VPSearchRecommendedCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
colors = [UIColor.colorA87A46(alpha: 0.18).cgColor, UIColor.colorA87A46(alpha: 0.05).cgColor, UIColor.colorA87A46(alpha: 0).cgColor]
locations = [0, 0.5, 1]
startPoint = .init(x: 0.5, y: 0)
endPoint = .init(x: 0.5, y: 1)
layer.cornerRadius = 10
layer.masksToBounds = true
self.layer.addSublayer(borderGradientLayout)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
borderGradientLayout.frame = self.bounds
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: borderShapeLayer.lineWidth / 2, dy: borderShapeLayer.lineWidth / 2), cornerRadius: layer.cornerRadius)
borderShapeLayer.path = path.cgPath
}
}
extension VPSearchRecommendedContentView {
private func vp_setupUI() {
addSubview(iconImageView)
addSubview(titleLabel)
addSubview(collectionView)
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.top.equalToSuperview().offset(10)
make.width.height.equalTo(18)
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalTo(iconImageView)
make.left.equalTo(iconImageView.snp.right).offset(4)
}
collectionView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(39)
make.bottom.equalToSuperview().offset(-10)
}
}
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
extension VPSearchRecommendedContentView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPSearchRecommendedCell
cell.model = dataArr[indexPath.row]
cell.row = indexPath.row
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = dataArr[indexPath.row]
let vc = VPDetailPlayerViewController()
vc.shortPlayId = model.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -0,0 +1,122 @@
//
// VPSearchRecommendedView.swift
// Veloria
//
// Created by on 2025/5/23.
//
import UIKit
class VPSearchRecommendedView: UIView {
override var intrinsicContentSize: CGSize {
return .init(width: UIScreen.width, height: UIScreen.height)
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.textColor = .colorFFFFFF()
label.text = "Recommended For You".localized
return label
}()
private lazy var scrollView: VPScrollView = {
let scrollView = VPScrollView()
scrollView.showsHorizontalScrollIndicator = false
return scrollView
}()
private lazy var stackView: UIStackView = {
let view = UIStackView(arrangedSubviews: [topView, newView])
view.axis = .horizontal
view.spacing = 20
return view
}()
private lazy var topView: VPSearchRecommendedContentView = {
let view = VPSearchRecommendedContentView()
view.borderColors = [UIColor.colorFBF2C7(alpha: 0.6).cgColor, UIColor.colorA8A38E(alpha: 0.4).cgColor, UIColor.color555555(alpha: 0.3).cgColor, UIColor.colorFBF2C7(alpha: 0.05).cgColor]
view.titleColors = [UIColor.colorFBF2C7().cgColor, UIColor.colorA87A46().cgColor]
view.iconImage = UIImage(named: "top_icon_01")
view.title = "Trending Top 10".localized
return view
}()
private lazy var newView: VPSearchRecommendedContentView = {
let view = VPSearchRecommendedContentView()
view.borderColors = [UIColor.colorFFBFBE(alpha: 0.6).cgColor, UIColor.colorFFBFBE(alpha: 0.4).cgColor, UIColor.color555555(alpha: 0.3).cgColor, UIColor.colorFFBFBE(alpha: 0.05).cgColor]
view.titleColors = [UIColor.colorFFBFBE().cgColor, UIColor.colorD87675().cgColor]
view.iconImage = UIImage(named: "hot_icon_02")
view.title = "Latest Trends".localized
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
requestTopData()
requestHotData()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPSearchRecommendedView {
private func vp_setupUI() {
addSubview(titleLabel)
addSubview(scrollView)
scrollView.addSubview(stackView)
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.equalToSuperview().offset(15)
}
scrollView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
make.top.equalToSuperview().offset(30)
}
stackView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-15)
make.top.equalToSuperview()
make.height.equalTo(self).offset(-(30 + UIScreen.tabbarSafeBottomMargin))
}
}
}
extension VPSearchRecommendedView {
private func requestTopData() {
VPHomeAPI.requestTopSearchList { [weak self] list in
guard let self = self else { return }
if let list = list {
self.topView.dataArr = list
}
}
}
private func requestHotData() {
VPHomeAPI.requestHotSearchList { [weak self] list in
guard let self = self else { return }
if let list = list {
self.newView.dataArr = list
}
}
}
}

View File

@ -0,0 +1,111 @@
//
// VPSearchResultCell.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPSearchResultCell: VPCollectionViewCell {
var searchText: String? {
didSet {
}
}
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
videoNameLabel.text = model?.name
desLabel.text = model?.vp_description
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)
}
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
imageView.layer.cornerRadius = 6
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var videoNameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 2
label.font = .fontMedium(ofSize: 14)
label.textColor = .colorFFFFFF()
return label
}()
private lazy var desLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 2
label.font = .fontRegular(ofSize: 11)
label.textColor = .colorFFFFFF(alpha: 0.6)
return label
}()
private lazy var hotView: UIButton = {
let view = JXButton(type: .custom)
view.isUserInteractionEnabled = false
view.titleDirection = .right
view.jx_font = .fontRegular(ofSize: 10)
view.setTitleColor(.colorDEDEDE(), for: .normal)
view.setImage(UIImage(named: "hot_icon_01"), for: .normal)
view.space = 1
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPSearchResultCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(videoNameLabel)
contentView.addSubview(desLabel)
contentView.addSubview(hotView)
coverImageView.snp.makeConstraints { make in
make.left.top.bottom.equalToSuperview()
make.width.equalTo(84)
}
videoNameLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(12)
make.right.lessThanOrEqualToSuperview()
make.centerY.equalTo(coverImageView.snp.top).offset(21)
}
desLabel.snp.makeConstraints { make in
make.left.equalTo(videoNameLabel)
make.right.lessThanOrEqualToSuperview()
make.bottom.equalToSuperview().offset(-3)
}
hotView.snp.makeConstraints { make in
make.left.equalTo(videoNameLabel)
make.top.equalToSuperview().offset(51)
}
}
}

View File

@ -0,0 +1,115 @@
//
// VPSearchResultView.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPSearchResultView: UIView {
var viewModel: VPSearchViewModel?
private lazy var dataArr: [VPShortModel] = []
private(set) lazy var searchText: String = ""
//MARK: UI
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 13)
label.textColor = .colorFFFFFF()
label.text = "Search Results".localized
return label
}()
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.minimumLineSpacing = 10
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(VPSearchResultCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func search(text: String) {
searchText = text
requestSearch(text: text)
}
}
extension VPSearchResultView {
private func vp_setupUI() {
addSubview(titleLabel)
addSubview(collectionView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(20)
}
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(10)
}
}
}
//MARK: -------------- UICollectionViewDataSource UICollectionViewDelegate --------------
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.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.viewController?.navigationController?.pushViewController(vc, animated: true)
self.viewModel?.addHistory(text: self.searchText)
}
}
extension VPSearchResultView {
private func requestSearch(text: String) {
VPHomeAPI.requestSearch(text: text) { [weak self] list in
guard let self = self else { return }
if self.searchText != text { return; }
if let list = list {
self.dataArr = list
self.collectionView.reloadData()
}
}
}
}

View File

@ -0,0 +1,60 @@
//
// VPSearchViewModel.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPSearchViewModel: NSObject {
@objc dynamic var historyArr: [String] = VPSearchViewModel.getSearchHistory()
func addHistory(text: String) {
guard text.count > 0 else { return }
VPSearchViewModel.addSearchHistory(text: text)
historyArr = VPSearchViewModel.getSearchHistory()
}
func cleanHistory() {
VPSearchViewModel.cleanSearchHistory()
historyArr.removeAll()
}
var searchHandle: ((_ text: String) -> Void)?
}
extension VPSearchViewModel {
///
static func addSearchHistory(text: String) {
var arr = getSearchHistory()
for (i, value) in arr.enumerated() {
if value == text {
arr.remove(at: i)
break
}
}
arr.insert(text, at: 0)
if arr.count > 10 {
arr.removeLast()
}
UserDefaults.standard.set(arr, forKey: kVPSearchHistoryDefaultsKey)
UserDefaults.standard.synchronize()
}
///
static func getSearchHistory() -> [String] {
let arr = UserDefaults.standard.object(forKey: kVPSearchHistoryDefaultsKey) as? [String]
return arr ?? []
}
///
static func cleanSearchHistory() {
UserDefaults.standard.set([], forKey: kVPSearchHistoryDefaultsKey)
UserDefaults.standard.synchronize()
}
}

View File

@ -19,7 +19,7 @@ class VPShortModel: VPModel, SmartCodable {
var all_coins: String?
var buy_type: String?
var collect_total: Int?
var sp_description: String?
var vp_description: String?
var episode_total: Int?
var horizontally_img: String?
var image_url: String?
@ -41,12 +41,12 @@ class VPShortModel: VPModel, SmartCodable {
@IgnoredKey
var titleAttributedString: NSAttributedString?
@IgnoredKey
var sp_isSelected: Bool?
var vp_isSelected: Bool?
static func mappingForKey() -> [SmartKeyTransformer]? {
return [
CodingKeys.sp_description <--- ["description"]
CodingKeys.vp_description <--- ["description"]
]
}
}

View File

@ -25,7 +25,7 @@ class VPEpisodeView: HWPanModalContentView {
} else {
tagView.isHidden = true
}
desLabel.text = shortModel?.sp_description
desLabel.text = shortModel?.vp_description
}
}

View File

@ -188,7 +188,7 @@ extension VPPlayerProgressView {
case .changed:
let point = sender.translation(in: self)
let offsetX = point.x / self.width
let offsetX = point.x / (self.width - self.insets.left - self.insets.right)
self.panProgress = self.tempProgress + offsetX
if self.panProgress < 0 {
self.panProgress = 0
@ -206,7 +206,7 @@ extension VPPlayerProgressView {
@objc func handleTapGesture(sender: UITapGestureRecognizer) {
let point = sender.location(in: self)
let offsetX = point.x / self.width
let offsetX = (point.x - self.insets.left) / (self.width - self.insets.left - self.insets.right)
self.panFinish?(offsetX)
}
}

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: 484 B

Binary file not shown.

After

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

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.6 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@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.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

After

Width:  |  Height:  |  Size: 689 B

View File

@ -12,9 +12,15 @@
"Explore" = "Explore";
"EP.%@" = "EP.%@";
"All %@ Episodes" = "All %@ Episodes";
"Recommended For You" = "Recommended For You";
"Trending Top 10" = "Trending Top 10";
"Latest Trends" = "Latest Trends";
"Search Results" = "Search Results";
"Recent Searches" = "Recent Searches";
"My List" = "My List";
"kHomeTitleText" = "10,000+ addictive shorts await!";
"kSearchPlaceholderText1" = "Search dramas";
"kSearchPlaceholderText2" = "#Recersal of fate";
"kHomeMenuTitle" = "Select Categories";

View File

@ -0,0 +1,307 @@
//
// JXTagView.swift
// YDLive
//
// Created by on 2020/11/7.
//
import UIKit
@objc protocol JXTagViewDataSource: NSObjectProtocol {
@objc optional func jx_tagView(tagView: JXTagView, titleForIndex index: Int) -> String?
@objc optional func jx_tagView(tagView: JXTagView, attributedTitleForIndex index: Int) -> NSAttributedString?
func jx_number(in tagView: JXTagView) -> Int
}
@objc protocol JXTagViewDelegate: NSObjectProtocol {
@objc optional func jx_tagView(tagView: JXTagView, didSelectedTagAt index: Int)
@objc optional func jx_tagView(tagView: JXTagView, enableForIndex index: Int) -> Bool
@objc optional func jx_tagView(tagView: JXTagView, selectedForIndex index: Int) -> Bool
}
class JXTagView: UIView {
enum LayoutDirection: Int {
case vertical = 0
case horizontal = 1
}
weak var delegate: JXTagViewDelegate?
weak var dataSource: JXTagViewDataSource?
var layoutDirection: LayoutDirection = .vertical
var tagBackgroundColor: UIColor = .systemBackground
var tagSelectedBackgroundColor: UIColor = .systemBackground
var tagDisabledBackgroundColor: UIColor = .systemBackground
var textFont: UIFont = .systemFont(ofSize: 14)
var textColor: UIColor = .colorFFFFFF()
var textSelectedColor: UIColor = .colorFFFFFF()
var textDisabledColor: UIColor = .gray
var tagCornerRadius: CGFloat = 14
var tagBorderWidth: CGFloat = 0
var tagBorderColor: UIColor?
var tagBorderSelectedColor: UIColor?
/// 0
var tagWidth: CGFloat = 0
var tagHeight: CGFloat = 28
var tagMinWidth: CGFloat = 0
///
var textMargin: CGFloat = 0
///
var tagHorizontalGap: CGFloat = 10
///
var tagVerticalGap: CGFloat = 10
///
var leftAndRightMargin: CGFloat = 15
///
var topAndBottomMargin: CGFloat = 0
///
private(set) var selectedIndex: Int?
private var oneReload = true
//MARK:-------------- 线 --------------
private(set) var buttonArr: [UIButton] = []
private var contentHeight: CGFloat = 0
override var intrinsicContentSize: CGSize {
let height = (self.buttonArr.last?.frame.maxY ?? 0) + self.topAndBottomMargin
if layoutDirection == .vertical {
return CGSize(width: UIScreen.main.bounds.size.width, height: height)
} else {
let width = (self.buttonArr.last?.frame.maxX ?? 0) + self.leftAndRightMargin
return CGSize(width: width, height: height)
}
}
// deinit {
// NotificationCenter.default.removeObserver(self)
// }
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if oneReload {
self.reloadData()
oneReload = false
}
self.buttonArr.enumerated().forEach {
let isEnble: Bool = self.delegate?.jx_tagView?(tagView: self, enableForIndex: $0) ?? true
let isSelected: Bool = self.delegate?.jx_tagView?(tagView: self, selectedForIndex: $0) ?? false
let button = $1
button.isSelected = isSelected
button.isEnabled = isEnble
if isSelected {
button.layer.borderColor = tagBorderSelectedColor?.cgColor
} else {
button.layer.borderColor = tagBorderColor?.cgColor
}
button.setBackgroundImage(UIImage(color: tagBackgroundColor), for: .normal)
button.setBackgroundImage(UIImage(color: tagSelectedBackgroundColor), for: .selected)
button.setBackgroundImage(UIImage(color: tagSelectedBackgroundColor), for: [.highlighted, .selected])
button.setBackgroundImage(UIImage(color: tagDisabledBackgroundColor), for: .disabled)
}
}
///
func updateState() {
self.setNeedsLayout()
self.layoutIfNeeded()
}
///
@objc func reloadData() {
selectedIndex = nil
for button in buttonArr {
button.removeFromSuperview()
}
buttonArr.removeAll()
let number = self.dataSource?.jx_number(in: self) ?? 0
if number == 0 {
self.invalidateIntrinsicContentSize()
return
}
for index in 0...(number - 1) {
let text = self.dataSource?.jx_tagView?(tagView: self, titleForIndex: index)
let attributedTitle = self.dataSource?.jx_tagView?(tagView: self, attributedTitleForIndex: index)
let isEnble: Bool = self.delegate?.jx_tagView?(tagView: self, enableForIndex: index) ?? true
let isSelected: Bool = self.delegate?.jx_tagView?(tagView: self, selectedForIndex: index) ?? false
let button = UIButton(type: .custom)
button.tag = index
button.setTitle(text, for: .normal)
button.setAttributedTitle(attributedTitle, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.setTitleColor(textSelectedColor, for: .selected)
button.setTitleColor(textSelectedColor, for: [.highlighted, .selected])
button.setTitleColor(textDisabledColor, for: .disabled)
button.titleLabel?.font = textFont
button.layer.cornerRadius = tagCornerRadius
button.layer.masksToBounds = true
button.layer.borderWidth = tagBorderWidth
button.addTarget(self, action: #selector(handleButton(sender:)), for: .touchUpInside)
button.isEnabled = isEnble
button.titleLabel?.lineBreakMode = .byTruncatingTail
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: textMargin, bottom: 0, right: textMargin)
//
button.isSelected = isSelected
if isSelected {
selectedIndex = index
}
self.addSubview(button)
self.buttonArr.append(button)
}
self.updateLayout()
}
///
func updateLayout() {
var x = leftAndRightMargin
var y = topAndBottomMargin
let height = tagHeight
var width: CGFloat = 0
for button in buttonArr {
if tagWidth <= 0 {
if let string = button.currentTitle {
width = string.size(font: textFont).width + (textMargin * 2)
} else if let string = button.currentAttributedTitle {
// width = string.size(font: textFont).width + (textMargin * 2)
width = string.size().width + textMargin * 2
}
//
if layoutDirection == .vertical {
if width > self.width - (leftAndRightMargin * 2) {
width = self.width - (leftAndRightMargin * 2)
}
}
//
if width < tagCornerRadius * 2 {
width = tagCornerRadius * 2
}
} else {
width = tagWidth
}
if width < tagMinWidth {
width = tagMinWidth
}
if layoutDirection == .vertical {
//
if Float(x + width + leftAndRightMargin) > Float(self.width) {
x = leftAndRightMargin
y = y + height + tagVerticalGap
}
}
button.frame = CGRect(x: x, y: y, width: width, height: height)
x = x + width + tagHorizontalGap
}
self.invalidateIntrinsicContentSize()
}
@objc func handleButton(sender: UIButton) {
self.delegate?.jx_tagView?(tagView: self, didSelectedTagAt: sender.tag)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateState()
}
// private func _setButtonBackgroundColor(button: UIButton, isSelected: Bool, isEnble: Bool) {
// if !isEnble {
// button.backgroundColor = tagDisabledBackgroundColor
// } else if isSelected {
// button.backgroundColor = tagSelectedBackgroundColor
// } else {
// button.backgroundColor = tagBackgroundColor
// }
//
// if isSelected {
// button.layer.borderColor = tagBorderSelectedColor?.cgColor
// } else {
// button.layer.borderColor = tagBorderColor?.cgColor
// }
//
// }
}
extension UIButton {
// private struct AssociatedKeys {
// static var borderColors: Int?
// }
func jx_setBackgroundImage(_ image: UIImage?, for state: UIControl.State) {
self.setBackgroundImage(image, for: state)
if state == .selected {
self.setBackgroundImage(image, for: [state, .highlighted])
}
}
func jx_setImage(_ image: UIImage?, for state: UIControl.State) {
self.setImage(image, for: state)
if state == .selected {
self.setImage(image, for: [state, .highlighted])
}
}
func jx_setTitle(_ title: String?, for state: UIControl.State) {
self.setTitle(title, for: state)
if state == .selected {
self.setTitle(title, for: [state, .highlighted])
}
}
func jx_setTitleColor(_ color: UIColor?, for state: UIControl.State) {
self.setTitleColor(color, for: state)
if state == .selected {
self.setTitleColor(color, for: [state, .highlighted])
}
}
}